diff options
author | Ned Batchelder <ned@nedbatchelder.com> | 2015-01-01 13:40:45 -0500 |
---|---|---|
committer | Ned Batchelder <ned@nedbatchelder.com> | 2015-01-01 13:40:45 -0500 |
commit | 08dfd5555a023cf8d2638e1f8e6cd948690523a5 (patch) | |
tree | 073bc66e16cfe3003a882d14ffb756b5ea98d4b8 /coverage | |
parent | ea231af0bbb3aa1eed780ece6c567857845d7c96 (diff) | |
download | python-coveragepy-git-08dfd5555a023cf8d2638e1f8e6cd948690523a5.tar.gz |
Move python source understanding into python.py
Diffstat (limited to 'coverage')
-rw-r--r-- | coverage/codeunit.py | 56 | ||||
-rw-r--r-- | coverage/control.py | 3 | ||||
-rw-r--r-- | coverage/execfile.py | 2 | ||||
-rw-r--r-- | coverage/files.py | 78 | ||||
-rw-r--r-- | coverage/parser.py | 2 | ||||
-rw-r--r-- | coverage/plugin.py | 2 | ||||
-rw-r--r-- | coverage/python.py | 136 |
7 files changed, 142 insertions, 137 deletions
diff --git a/coverage/codeunit.py b/coverage/codeunit.py index e9efa396..998aa098 100644 --- a/coverage/codeunit.py +++ b/coverage/codeunit.py @@ -1,12 +1,9 @@ """Code unit (module) handling for Coverage.""" import os -import sys from coverage.backward import unicode_class -from coverage.files import get_python_source, FileLocator -from coverage.parser import PythonParser -from coverage.phystokens import source_token_lines, source_encoding +from coverage.files import FileLocator class CodeUnit(object): @@ -114,54 +111,3 @@ class CodeUnit(object): def get_parser(self, exclude=None): raise NotImplementedError - - -class PythonCodeUnit(CodeUnit): - """Represents a Python file.""" - - def __init__(self, morf, file_locator=None): - super(PythonCodeUnit, self).__init__(morf, file_locator) - self._source = None - - 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 source(self): - if self._source is None: - self._source = get_python_source(self.filename) - if sys.version_info < (3, 0): - encoding = source_encoding(self._source) - self._source = self._source.decode(encoding, "replace") - assert isinstance(self._source, unicode_class) - return self._source - - def get_parser(self, exclude=None): - return PythonParser(filename=self.filename, exclude=exclude) - - 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. - - """ - # Get the file extension. - _, ext = os.path.splitext(self.filename) - - # Anything named *.py* should be Python. - if ext.startswith('.py'): - return True - # A file with no extension should be Python. - if not ext: - return True - # Everything else is probably not Python. - return False - - def source_token_lines(self): - return source_token_lines(self.source()) diff --git a/coverage/control.py b/coverage/control.py index 0ca1e95c..4aaf1af3 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -10,7 +10,7 @@ import sys from coverage.annotate import AnnotateReporter from coverage.backward import string_class, iitems -from coverage.codeunit import CodeUnit, PythonCodeUnit +from coverage.codeunit import CodeUnit from coverage.collector import Collector from coverage.config import CoverageConfig from coverage.data import CoverageData @@ -22,6 +22,7 @@ from coverage.files import ModuleMatcher from coverage.html import HtmlReporter from coverage.misc import CoverageException, bool_or_none, join_regex from coverage.misc import file_be_gone, overrides +from coverage.python import PythonCodeUnit from coverage.results import Analysis, Numbers from coverage.summary import SummaryReporter from coverage.xmlreport import XmlReporter diff --git a/coverage/execfile.py b/coverage/execfile.py index fc7ad174..2d856897 100644 --- a/coverage/execfile.py +++ b/coverage/execfile.py @@ -7,8 +7,8 @@ import types from coverage.backward import BUILTINS from coverage.backward import PYC_MAGIC_NUMBER, imp, importlib_util_find_spec -from coverage.files import get_python_source from coverage.misc import ExceptionDuringRun, NoCode, NoSource +from coverage.python import get_python_source class DummyLoader(object): diff --git a/coverage/files.py b/coverage/files.py index 9f0827fa..15ccabce 100644 --- a/coverage/files.py +++ b/coverage/files.py @@ -7,10 +7,8 @@ import os.path import posixpath import re import sys -import tokenize -from coverage.misc import CoverageException, NoSource, join_regex -from coverage.phystokens import source_encoding +from coverage.misc import CoverageException, join_regex class FileLocator(object): @@ -56,80 +54,6 @@ class FileLocator(object): return self.canonical_filename_cache[filename] -def read_python_source(filename): - """Read the Python source text from `filename`. - - Returns a str: unicode on Python 3, bytes on Python 2. - - """ - # Python 3.2 provides `tokenize.open`, the best way to open source files. - if sys.version_info >= (3, 2): - f = tokenize.open(filename) - else: - f = open(filename, "rU") - - with f: - return f.read() - - -def get_python_source(filename): - """Return the source code, as a str.""" - base, ext = os.path.splitext(filename) - if ext == ".py" and sys.platform == "win32": - exts = [".py", ".pyw"] - else: - exts = [ext] - - for ext in exts: - try_filename = base + ext - if os.path.exists(try_filename): - # A regular text file: open it. - source = read_python_source(try_filename) - break - - # Maybe it's in a zip file? - source = get_zip_bytes(try_filename) - if source is not None: - if sys.version_info >= (3, 0): - source = source.decode(source_encoding(source)) - break - else: - # Couldn't find source. - raise NoSource("No source for code: '%s'." % filename) - - # Python code should always end with a line with a newline. - if source and source[-1] != '\n': - source += '\n' - - return source - - -def get_zip_bytes(filename): - """Get data from `filename` if it is a zip file path. - - Returns the bytestring data read from the zip file, or None if no zip file - could be found or `filename` isn't in it. The data returned will be - an empty string if the file is empty. - - """ - import zipimport - markers = ['.zip'+os.sep, '.egg'+os.sep] - for marker in markers: - if marker in filename: - parts = filename.split(marker) - try: - zi = zipimport.zipimporter(parts[0]+marker[:-1]) - except zipimport.ZipImportError: - continue - try: - data = zi.get_data(parts[1]) - except IOError: - continue - assert isinstance(data, bytes) - return data - return None - - if sys.platform == 'win32': def actual_path(path): diff --git a/coverage/parser.py b/coverage/parser.py index a2fa1dfe..f488367d 100644 --- a/coverage/parser.py +++ b/coverage/parser.py @@ -9,7 +9,6 @@ import tokenize from coverage.backward import range # pylint: disable=redefined-builtin from coverage.backward import bytes_to_ints from coverage.bytecode import ByteCodes, CodeObjects -from coverage.files import get_python_source from coverage.misc import nice_pair, expensive, join_regex from coverage.misc import CoverageException, NoSource, NotPython from coverage.phystokens import generate_tokens @@ -46,6 +45,7 @@ class PythonParser(CodeParser): self.filename = filename or "<code>" self.text = text if not self.text: + from coverage.python import get_python_source try: self.text = get_python_source(self.filename) except IOError as err: diff --git a/coverage/plugin.py b/coverage/plugin.py index a0a65e44..362e561a 100644 --- a/coverage/plugin.py +++ b/coverage/plugin.py @@ -1,7 +1,5 @@ """Plugin management for coverage.py""" -import sys - class CoveragePlugin(object): """Base class for coverage.py plugins.""" diff --git a/coverage/python.py b/coverage/python.py new file mode 100644 index 00000000..62376922 --- /dev/null +++ b/coverage/python.py @@ -0,0 +1,136 @@ +"""Python source expertise for coverage.py""" + +import os.path +import sys +import tokenize +import zipimport + +from coverage.backward import unicode_class +from coverage.codeunit import CodeUnit +from coverage.misc import NoSource +from coverage.parser import PythonParser +from coverage.phystokens import source_token_lines, source_encoding + + +def read_python_source(filename): + """Read the Python source text from `filename`. + + Returns a str: unicode on Python 3, bytes on Python 2. + + """ + # Python 3.2 provides `tokenize.open`, the best way to open source files. + if sys.version_info >= (3, 2): + f = tokenize.open(filename) + else: + f = open(filename, "rU") + + with f: + return f.read() + + +def get_python_source(filename): + """Return the source code, as a str.""" + base, ext = os.path.splitext(filename) + if ext == ".py" and sys.platform == "win32": + exts = [".py", ".pyw"] + else: + exts = [ext] + + for ext in exts: + try_filename = base + ext + if os.path.exists(try_filename): + # A regular text file: open it. + source = read_python_source(try_filename) + break + + # Maybe it's in a zip file? + source = get_zip_bytes(try_filename) + if source is not None: + if sys.version_info >= (3, 0): + source = source.decode(source_encoding(source)) + break + else: + # Couldn't find source. + raise NoSource("No source for code: '%s'." % filename) + + # Python code should always end with a line with a newline. + if source and source[-1] != '\n': + source += '\n' + + return source + + +def get_zip_bytes(filename): + """Get data from `filename` if it is a zip file path. + + Returns the bytestring data read from the zip file, or None if no zip file + could be found or `filename` isn't in it. The data returned will be + an empty string if the file is empty. + + """ + markers = ['.zip'+os.sep, '.egg'+os.sep] + for marker in markers: + if marker in filename: + parts = filename.split(marker) + try: + zi = zipimport.zipimporter(parts[0]+marker[:-1]) + except zipimport.ZipImportError: + continue + try: + data = zi.get_data(parts[1]) + except IOError: + continue + assert isinstance(data, bytes) + return data + return None + + +class PythonCodeUnit(CodeUnit): + """Represents a Python file.""" + + def __init__(self, morf, file_locator=None): + super(PythonCodeUnit, self).__init__(morf, file_locator) + self._source = None + + 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 source(self): + if self._source is None: + self._source = get_python_source(self.filename) + if sys.version_info < (3, 0): + encoding = source_encoding(self._source) + self._source = self._source.decode(encoding, "replace") + assert isinstance(self._source, unicode_class) + return self._source + + def get_parser(self, exclude=None): + return PythonParser(filename=self.filename, exclude=exclude) + + 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. + + """ + # Get the file extension. + _, ext = os.path.splitext(self.filename) + + # Anything named *.py* should be Python. + if ext.startswith('.py'): + return True + # A file with no extension should be Python. + if not ext: + return True + # Everything else is probably not Python. + return False + + def source_token_lines(self): + return source_token_lines(self.source()) |