diff options
Diffstat (limited to 'coverage')
-rw-r--r-- | coverage/backward.py | 2 | ||||
-rw-r--r-- | coverage/codeunit.py | 87 | ||||
-rw-r--r-- | coverage/control.py | 6 | ||||
-rw-r--r-- | coverage/parser.py | 79 | ||||
-rw-r--r-- | coverage/results.py | 42 |
5 files changed, 105 insertions, 111 deletions
diff --git a/coverage/backward.py b/coverage/backward.py index a0dc902..03fa651 100644 --- a/coverage/backward.py +++ b/coverage/backward.py @@ -8,7 +8,7 @@ import os, re, sys -# Pythons 2 and 3 differ on where to get StringIO +# Pythons 2 and 3 differ on where to get StringIO. try: from cStringIO import StringIO BytesIO = StringIO diff --git a/coverage/codeunit.py b/coverage/codeunit.py index f19a179..d9cd5e4 100644 --- a/coverage/codeunit.py +++ b/coverage/codeunit.py @@ -3,9 +3,8 @@ import glob, os, re from coverage.backward import open_source, string_class, StringIO -from coverage.misc import CoverageException -from coverage.parser import CodeParser -from coverage.results import Analysis +from coverage.misc import CoverageException, NoSource +from coverage.parser import CodeParser, PythonParser from coverage.phystokens import source_token_lines, source_encoding @@ -67,7 +66,7 @@ class CodeUnit(object): f = morf.__file__ else: f = morf - f = self.adjust_filename(f) + f = self._adjust_filename(f) self.filename = self.file_locator.canonical_filename(f) if hasattr(morf, '__name__'): @@ -88,9 +87,6 @@ class CodeUnit(object): def __repr__(self): return "<CodeUnit name=%r filename=%r>" % (self.name, self.filename) - def adjust_filename(self, fname): - return fname - # Annoying comparison operators. Py3k wants __lt__ etc, and Py2k needs all # of them defined. @@ -114,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: @@ -152,10 +148,9 @@ class CodeUnit(object): class PythonCodeUnit(CodeUnit): """Represents a Python file.""" - analysis_class = Analysis - parser_class = CodeParser + parser_class = PythonParser - def adjust_filename(self, fname): + def _adjust_filename(self, fname): # .pyc files should always refer to a .py instead. if fname.endswith(('.pyc', '.pyo')): fname = fname[:-1] @@ -163,6 +158,44 @@ class PythonCodeUnit(CodeUnit): 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? @@ -202,7 +235,7 @@ def mako_template_name(py_filename): return template_filename -class MakoParser(object): +class MakoParser(CodeParser): def __init__(self, cu, text, filename, exclude): self.cu = cu self.text = text @@ -235,20 +268,16 @@ class MakoParser(object): tlines.remove(-1) return tlines - def first_lines(self, lines, *ignores): - return set(lines) - - def first_line(self, line): - return line - def exit_counts(self): - return {} - - def arcs(self): - return [] +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) -class MakoAnalysis(Analysis): + def source_file(self): + return open(self.mako_filename) def find_source(self, filename): """Find the source for `filename`. @@ -262,18 +291,6 @@ class MakoAnalysis(Analysis): return mako_filename, source - -class MakoCodeUnit(CodeUnit): - analysis_class = MakoAnalysis - 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 source_token_lines(self, source): """Return the 'tokenized' text for the code.""" for line in source.splitlines(): diff --git a/coverage/control.py b/coverage/control.py index e71547a..07551ff 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -4,7 +4,7 @@ import atexit, os, random, socket, sys from coverage.annotate import AnnotateReporter from coverage.backward import string_class, iitems -from coverage.codeunit import code_unit_factory, CodeUnit +from coverage.codeunit import code_unit_factory, CodeUnit, PythonCodeUnit from coverage.collector import Collector from coverage.config import CoverageConfig from coverage.data import CoverageData @@ -214,7 +214,7 @@ class coverage(object): def _canonical_dir(self, morf): """Return the canonical directory of the module or file `morf`.""" - return os.path.split(CodeUnit(morf, self.file_locator).filename)[0] + return os.path.split(PythonCodeUnit(morf, self.file_locator).filename)[0] def _source_for_file(self, filename): """Return the source file for `filename`.""" @@ -595,7 +595,7 @@ class coverage(object): if not isinstance(it, CodeUnit): it = code_unit_factory(it, self.file_locator)[0] - return it.analysis_class(self, it) + return Analysis(self, it) def report(self, morfs=None, show_missing=True, ignore_errors=None, file=None, # pylint: disable=W0622 diff --git a/coverage/parser.py b/coverage/parser.py index 88f6f29..f569de2 100644 --- a/coverage/parser.py +++ b/coverage/parser.py @@ -11,6 +11,47 @@ from coverage.misc import CoverageException, NoSource, NotPython class CodeParser(object): + """ + Base class for any code parser. + """ + def _adjust_filename(self, fname): + return fname + + def first_lines(self, lines, *ignores): + """Map the line numbers in `lines` to the correct first line of the + statement. + + Skip any line mentioned in any of the sequences in `ignores`. + + Returns a set of the first lines. + + """ + ignore = set() + for ign in ignores: + ignore.update(ign) + lset = set() + for l in lines: + if l in ignore: + continue + new_l = self.first_line(l) + if new_l not in ignore: + lset.add(new_l) + return lset + + def first_line(self, line): + return line + + def translate_lines(self, lines): + return lines + + def exit_counts(self): + return {} + + def arcs(self): + return [] + + +class PythonParser(CodeParser): """Parse code to find executable lines, excluded lines, etc.""" def __init__(self, cu, text=None, filename=None, exclude=None): @@ -20,7 +61,7 @@ class CodeParser(object): `exclude`, a regex. """ - assert text or filename, "CodeParser needs either text or filename" + assert text or filename, "PythonParser needs either text or filename" self.filename = filename or "<code>" self.text = text if not self.text: @@ -137,9 +178,8 @@ class CodeParser(object): # We're at the end of a line, and we've ended on a # different line than the first line of the statement, # so record a multi-line range. - rng = (first_line, elineno) for l in range(first_line, elineno+1): - self.multiline[l] = rng + self.multiline[l] = first_line first_line = None if ttext.strip() and toktype != tokenize.COMMENT: @@ -161,38 +201,13 @@ class CodeParser(object): if not empty: self.statement_starts.update(self.byte_parser._find_statements()) - def translate_lines(self, lines): - return lines - def first_line(self, line): """Return the first line number of the statement including `line`.""" - rng = self.multiline.get(line) - if rng: - first_line = rng[0] + first_line = self.multiline.get(line) + if first_line: + return first_line else: - first_line = line - return first_line - - def first_lines(self, lines, *ignores): - """Map the line numbers in `lines` to the correct first line of the - statement. - - Skip any line mentioned in any of the sequences in `ignores`. - - Returns a set of the first lines. - - """ - ignore = set() - for ign in ignores: - ignore.update(ign) - lset = set() - for l in lines: - if l in ignore: - continue - new_l = self.first_line(l) - if new_l not in ignore: - lset.add(new_l) - return lset + return line def parse_source(self): """Parse source text to find executable lines, excluded lines, etc. diff --git a/coverage/results.py b/coverage/results.py index 8cac147..79615c7 100644 --- a/coverage/results.py +++ b/coverage/results.py @@ -4,7 +4,7 @@ import collections import os from coverage.backward import iitems -from coverage.misc import format_lines, join_regex, NoSource +from coverage.misc import format_lines, join_regex class Analysis(object): @@ -15,7 +15,7 @@ class Analysis(object): self.code_unit = code_unit self.filename = self.code_unit.filename - actual_filename, source = self.find_source(self.filename) + actual_filename, source = self.code_unit.find_source(self.filename) self.parser = code_unit.parser_class( code_unit, @@ -54,44 +54,6 @@ class Analysis(object): n_missing_branches=n_missing_branches, ) - 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.coverage.file_locator.get_zip_data(try_filename) - if source: - return try_filename, source - raise NoSource("No source for code: '%s'" % filename) - def missing_formatted(self): """The missing line numbers, formatted nicely. |