|
| 1 | +"""Python dialect control. |
| 2 | +
|
| 3 | +This may be incomplet or inkorrekt. |
| 4 | +
|
| 5 | +Versions < 2.7 is not a priority. |
| 6 | +Versions >= 3.0 and < 3.2 are not a priority. |
| 7 | +Versions < 2.4 have many fundamental differences. |
| 8 | +Versions < 2.2 have even more fundamental differences. |
| 9 | +Versions 1.6 and 2.0 are not distinguished. |
| 10 | +Versions < 1.6 are not even mentioned. |
| 11 | +""" |
| 12 | + |
| 13 | + |
| 14 | +from typing import ( |
| 15 | + List, |
| 16 | + Sequence, |
| 17 | + Set, |
| 18 | + Tuple, |
| 19 | +) |
| 20 | + |
| 21 | +import os |
| 22 | +import subprocess |
| 23 | +import sys |
| 24 | + |
| 25 | + |
| 26 | +# Maintain our own copy instead of using __future__ just in case we need |
| 27 | +# to parse a newer version of python than we're running. |
| 28 | +# Also we can add artificial ones. |
| 29 | + |
| 30 | +available_futures = { |
| 31 | + # Internal pseudo-futures. |
| 32 | + 'mypy-codec': '0.0', |
| 33 | + 'mypy-stub': '0.0', |
| 34 | + 'mypy-instring': '0.0', |
| 35 | + |
| 36 | + # Pseudo-futures from platform.python_implementation(). |
| 37 | + # Note that only PyPy and CPython are tested. |
| 38 | + 'variant-CPython': '0.0', |
| 39 | + 'variant-PyPy': '0.0', |
| 40 | + 'variant-Jython': '0.0', |
| 41 | + 'variant-IronPython': '0.0', |
| 42 | + |
| 43 | + # Real features from the __future__ module. |
| 44 | + 'nested_scopes': '2.1', |
| 45 | + 'generators': '2.2', |
| 46 | + 'division': '2.2', |
| 47 | + 'absolute_import': '2.5', |
| 48 | + 'with_statement': '2.5', |
| 49 | + 'print_function': '2.6', |
| 50 | + 'unicode_literals': '2.6', |
| 51 | + 'barry_as_FLUFL': '3.1', |
| 52 | +} |
| 53 | + |
| 54 | + |
| 55 | +def check_futures(version: str, futures: Sequence[str]) -> Set[str]: |
| 56 | + for fut in futures: |
| 57 | + assert version >= available_futures[fut] |
| 58 | + return set(futures) |
| 59 | + |
| 60 | + |
| 61 | +class Dialect: |
| 62 | + |
| 63 | + def __init__(self, version: str, future_list: Sequence[str] = []) -> None: |
| 64 | + """Construct a dialect for the given Python version and future set. |
| 65 | +
|
| 66 | + `version` is like `'2.7.0'`, e.g. `platform.python_version()`. |
| 67 | + `future_list` is like `['X', 'Y', 'Z']` in `from __future__ import X, Y, Z`. |
| 68 | + """ |
| 69 | + self.major, self.minor, self.patchlevel = [int(x) for x in version.split('.')] |
| 70 | + future_set = check_futures(version, future_list) |
| 71 | + self.base_version = version |
| 72 | + self.base_future_list = future_list |
| 73 | + self.base_future_set = future_set |
| 74 | + |
| 75 | + self.possible_futures = {k for (k, v) in available_futures.items() if v <= version} |
| 76 | + |
| 77 | + # Additional members will be set as needed by the lexer, parser, etc. |
| 78 | + |
| 79 | + def __repr__(self) -> str: |
| 80 | + return 'Dialect(%r, %r)' % (self.base_version, self.base_future_list) |
| 81 | + |
| 82 | + def add_future(self, future: str) -> 'Dialect': |
| 83 | + if future in self.base_future_set: |
| 84 | + return self |
| 85 | + return Dialect(self.base_version, self.base_future_list + [future]) |
| 86 | + |
| 87 | + |
| 88 | +class Implementation: |
| 89 | + |
| 90 | + def __init__(self, executable: str) -> None: |
| 91 | + command = '''if True: |
| 92 | + import platform, sys |
| 93 | + print((platform.python_implementation(), platform.python_version(), sys.path)) |
| 94 | + ''' |
| 95 | + output = subprocess.check_output([executable, '-c', command]) |
| 96 | + impl, version, path = eval(output) # type: Tuple[str, str, List[str]] |
| 97 | + |
| 98 | + self.executable = executable |
| 99 | + self.base_dialect = Dialect(version, ['variant-' + impl]) |
| 100 | + self.stub_dialect = self.base_dialect.add_future('mypy-codec') |
| 101 | + self.python_path = path |
| 102 | + # TODO self.stub_path = [] |
| 103 | + |
| 104 | + |
| 105 | +def default_implementation(*, force_py2: bool = False) -> Implementation: |
| 106 | + """Return the preferred python implementation for the inferior. |
| 107 | +
|
| 108 | + This looks at the MYPY_PYTHON environment variable, or else uses |
| 109 | + the current python version. |
| 110 | +
|
| 111 | + The `force_py2` argument should possibly be deprecated. |
| 112 | + """ |
| 113 | + if force_py2: |
| 114 | + try_pythons = [os.getenv('MYPY_PYTHON'), 'python2', 'python2.7', 'python'] |
| 115 | + else: |
| 116 | + try_pythons = [os.getenv('MYPY_PYTHON'), sys.executable] |
| 117 | + for python in try_pythons: |
| 118 | + if python is None: |
| 119 | + continue |
| 120 | + try: |
| 121 | + impl = Implementation(python) |
| 122 | + except (OSError, subprocess.CalledProcessError): |
| 123 | + pass |
| 124 | + if force_py2 and impl.base_dialect.major != 2: |
| 125 | + continue |
| 126 | + return impl |
| 127 | + sys.exit('No suitable python executable found') |
0 commit comments