Skip to content

Commit

Permalink
Further consolidation of code reading Python source.
Browse files Browse the repository at this point in the history
  • Loading branch information
nedbat committed Dec 28, 2014
1 parent 98a7b99 commit 93561c7
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 38 deletions.
11 changes: 3 additions & 8 deletions coverage/execfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@

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.phystokens import read_python_source


class DummyLoader(object):
Expand Down Expand Up @@ -178,16 +178,11 @@ def make_code_from_py(filename):
"""Get source from `filename` and make a code object of it."""
# Open the source file.
try:
source = read_python_source(filename)
except IOError:
source = get_python_source(filename)
except (IOError, NoSource):
raise NoSource("No file to run: %r" % filename)

# We have the source. `compile` still needs the last line to be clean,
# so make sure it is, then compile a code object from it.
if not source or source[-1] != '\n':
source += '\n'
code = compile(source, filename, "exec")

return code


Expand Down
38 changes: 31 additions & 7 deletions coverage/files.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
"""File wrangling."""

import fnmatch
import ntpath
import os
import os.path
import posixpath
import re
import sys
import ntpath
import posixpath
import tokenize

from coverage.misc import CoverageException, NoSource, join_regex
from coverage.phystokens import read_python_source, source_encoding
from coverage.phystokens import source_encoding


class FileLocator(object):
Expand Down Expand Up @@ -55,6 +56,22 @@ def canonical_filename(self, filename):
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)
Expand All @@ -67,17 +84,24 @@ def get_python_source(filename):
try_filename = base + ext
if os.path.exists(try_filename):
# A regular text file: open it.
return read_python_source(try_filename)
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))
return source
break
else:
# Couldn't find source.
raise NoSource("No source for code: %r." % filename)

# Python code should always end with a line with a newline.
if source and source[-1] != '\n':
source += '\n'

# Couldn't find source.
raise NoSource("No source for code: %r." % filename)
return source


def get_zip_bytes(filename):
Expand Down
14 changes: 7 additions & 7 deletions coverage/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,10 @@ def __init__(self, text=None, filename=None, exclude=None):
if self.text:
assert isinstance(self.text, str)
# Scrap the BOM if it exists.
if ord(self.text[0]) == 0xfeff:
self.text = self.text[1:]
# Python source should always have a final newline.
if self.text[-1] != "\n":
self.text += "\n"
# (Used to do this, but no longer. Not sure what bad will happen
# if we don't do it.)
# if ord(self.text[0]) == 0xfeff:
# self.text = self.text[1:]

self.exclude = exclude

Expand Down Expand Up @@ -352,9 +351,10 @@ def __init__(self, text, code=None, filename=None):
self.code = compile(text, filename, "exec")
except SyntaxError as synerr:
raise NotPython(
"Couldn't parse '%s' as Python source: '%s' at line %d" %
(filename, synerr.msg, synerr.lineno)
"Couldn't parse %r as Python source: '%s' at line %d" % (
filename, synerr.msg, synerr.lineno
)
)

# Alternative Python implementations don't always provide all the
# attributes on code objects that we need to do the analysis.
Expand Down
16 changes: 0 additions & 16 deletions coverage/phystokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,19 +262,3 @@ def _source_encoding_py3(source):
source_encoding = _source_encoding_py3
else:
source_encoding = _source_encoding_py2


def read_python_source(filename):
"""Read the Python source text from `filename`.
Returns 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()

0 comments on commit 93561c7

Please sign in to comment.