blob: e5c9e5e0275f6ad0ca82ff6f71014961f3e9319d [file] [log] [blame]
"""Utility functions with no non-trivial dependencies."""
import re
import subprocess
from xml.sax.saxutils import escape
from typing import TypeVar, List, Tuple, Optional, Sequence
T = TypeVar('T')
ENCODING_RE = re.compile(br'([ \t\v]*#.*(\r\n?|\n))??[ \t\v]*#.*coding[:=][ \t]*([-\w.]+)')
default_python2_interpreter = ['python2', 'python', '/usr/bin/python']
def split_module_names(mod_name: str) -> List[str]:
"""Return the module and all parent module names.
So, if `mod_name` is 'a.b.c', this function will return
['a.b.c', 'a.b', and 'a'].
"""
out = [mod_name]
while '.' in mod_name:
mod_name = mod_name.rsplit('.', 1)[0]
out.append(mod_name)
return out
def short_type(obj: object) -> str:
"""Return the last component of the type name of an object.
If obj is None, return 'nil'. For example, if obj is 1, return 'int'.
"""
if obj is None:
return 'nil'
t = str(type(obj))
return t.split('.')[-1].rstrip("'>")
def indent(s: str, n: int) -> str:
"""Indent all the lines in s (separated by Newlines) by n spaces."""
s = ' ' * n + s
s = s.replace('\n', '\n' + ' ' * n)
return s
def array_repr(a: List[T]) -> List[str]:
"""Return the items of an array converted to strings using Repr."""
aa = [] # type: List[str]
for x in a:
aa.append(repr(x))
return aa
def dump_tagged(nodes: Sequence[object], tag: str) -> str:
"""Convert an array into a pretty-printed multiline string representation.
The format is
tag(
item1..
itemN)
Individual items are formatted like this:
- arrays are flattened
- pairs (str : array) are converted recursively, so that str is the tag
- other items are converted to strings and indented
"""
a = [] # type: List[str]
if tag:
a.append(tag + '(')
for n in nodes:
if isinstance(n, list):
if n:
a.append(dump_tagged(n, None))
elif isinstance(n, tuple):
s = dump_tagged(n[1], n[0])
a.append(indent(s, 2))
elif n:
a.append(indent(str(n), 2))
if tag:
a[-1] += ')'
return '\n'.join(a)
def find_python_encoding(text: bytes, pyversion: Tuple[int, int]) -> Tuple[str, int]:
"""PEP-263 for detecting Python file encoding"""
result = ENCODING_RE.match(text)
if result:
line = 2 if result.group(1) else 1
encoding = result.group(3).decode('ascii')
# Handle some aliases that Python is happy to accept and that are used in the wild.
if encoding.startswith(('iso-latin-1-', 'latin-1-')) or encoding == 'iso-latin-1':
encoding = 'latin-1'
return encoding, line
else:
default_encoding = 'utf8' if pyversion[0] >= 3 else 'ascii'
return default_encoding, -1
_python2_interpreter = None # type: Optional[str]
def try_find_python2_interpreter() -> Optional[str]:
global _python2_interpreter
if _python2_interpreter:
return _python2_interpreter
for interpreter in default_python2_interpreter:
try:
process = subprocess.Popen([interpreter, '-V'], stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
stdout, stderr = process.communicate()
if b'Python 2.7' in stdout:
_python2_interpreter = interpreter
return interpreter
except OSError:
pass
return None
PASS_TEMPLATE = """<?xml version="1.0" encoding="utf-8"?>
<testsuite errors="0" failures="0" name="mypy" skips="0" tests="1" time="{time:.3f}">
<testcase classname="mypy" file="mypy" line="1" name="mypy" time="{time:.3f}">
</testcase>
</testsuite>
"""
FAIL_TEMPLATE = """<?xml version="1.0" encoding="utf-8"?>
<testsuite errors="0" failures="1" name="mypy" skips="0" tests="1" time="{time:.3f}">
<testcase classname="mypy" file="mypy" line="1" name="mypy" time="{time:.3f}">
<failure message="mypy produced messages">{text}</failure>
</testcase>
</testsuite>
"""
ERROR_TEMPLATE = """<?xml version="1.0" encoding="utf-8"?>
<testsuite errors="1" failures="0" name="mypy" skips="0" tests="1" time="{time:.3f}">
<testcase classname="mypy" file="mypy" line="1" name="mypy" time="{time:.3f}">
<error message="mypy produced errors">{text}</error>
</testcase>
</testsuite>
"""
def write_junit_xml(dt: float, serious: bool, messages: List[str], path: str) -> None:
"""XXX"""
if not messages and not serious:
xml = PASS_TEMPLATE.format(time=dt)
elif not serious:
xml = FAIL_TEMPLATE.format(text=escape('\n'.join(messages)), time=dt)
else:
xml = ERROR_TEMPLATE.format(text=escape('\n'.join(messages)), time=dt)
with open(path, 'wb') as f:
f.write(xml.encode('utf-8'))