Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 1 | """Parser for the environment markers micro-language defined in PEP 345.""" |
| 2 | |
| 3 | import sys |
| 4 | import platform |
| 5 | import os |
| 6 | |
| 7 | from tokenize import tokenize, NAME, OP, STRING, ENDMARKER, ENCODING |
| 8 | from io import BytesIO |
| 9 | |
| 10 | __all__ = ['interpret'] |
| 11 | |
| 12 | |
| 13 | # allowed operators |
| 14 | _OPERATORS = {'==': lambda x, y: x == y, |
| 15 | '!=': lambda x, y: x != y, |
| 16 | '>': lambda x, y: x > y, |
| 17 | '>=': lambda x, y: x >= y, |
| 18 | '<': lambda x, y: x < y, |
| 19 | '<=': lambda x, y: x <= y, |
| 20 | 'in': lambda x, y: x in y, |
| 21 | 'not in': lambda x, y: x not in y} |
| 22 | |
| 23 | |
| 24 | def _operate(operation, x, y): |
| 25 | return _OPERATORS[operation](x, y) |
| 26 | |
| 27 | |
| 28 | # restricted set of variables |
| 29 | _VARS = {'sys.platform': sys.platform, |
| 30 | 'python_version': sys.version[:3], |
| 31 | 'python_full_version': sys.version.split(' ', 1)[0], |
| 32 | 'os.name': os.name, |
| 33 | 'platform.version': platform.version(), |
| 34 | 'platform.machine': platform.machine(), |
| 35 | 'platform.python_implementation': platform.python_implementation()} |
| 36 | |
| 37 | |
| 38 | class _Operation: |
| 39 | |
| 40 | def __init__(self, execution_context=None): |
| 41 | self.left = None |
| 42 | self.op = None |
| 43 | self.right = None |
| 44 | if execution_context is None: |
| 45 | execution_context = {} |
| 46 | self.execution_context = execution_context |
| 47 | |
| 48 | def _get_var(self, name): |
| 49 | if name in self.execution_context: |
| 50 | return self.execution_context[name] |
| 51 | return _VARS[name] |
| 52 | |
| 53 | def __repr__(self): |
| 54 | return '%s %s %s' % (self.left, self.op, self.right) |
| 55 | |
| 56 | def _is_string(self, value): |
| 57 | if value is None or len(value) < 2: |
| 58 | return False |
| 59 | for delimiter in '"\'': |
| 60 | if value[0] == value[-1] == delimiter: |
| 61 | return True |
| 62 | return False |
| 63 | |
| 64 | def _is_name(self, value): |
| 65 | return value in _VARS |
| 66 | |
| 67 | def _convert(self, value): |
| 68 | if value in _VARS: |
| 69 | return self._get_var(value) |
| 70 | return value.strip('"\'') |
| 71 | |
| 72 | def _check_name(self, value): |
| 73 | if value not in _VARS: |
| 74 | raise NameError(value) |
| 75 | |
| 76 | def _nonsense_op(self): |
| 77 | msg = 'This operation is not supported : "%s"' % self |
| 78 | raise SyntaxError(msg) |
| 79 | |
| 80 | def __call__(self): |
| 81 | # make sure we do something useful |
| 82 | if self._is_string(self.left): |
| 83 | if self._is_string(self.right): |
| 84 | self._nonsense_op() |
| 85 | self._check_name(self.right) |
| 86 | else: |
| 87 | if not self._is_string(self.right): |
| 88 | self._nonsense_op() |
| 89 | self._check_name(self.left) |
| 90 | |
| 91 | if self.op not in _OPERATORS: |
| 92 | raise TypeError('Operator not supported "%s"' % self.op) |
| 93 | |
| 94 | left = self._convert(self.left) |
| 95 | right = self._convert(self.right) |
| 96 | return _operate(self.op, left, right) |
| 97 | |
| 98 | |
| 99 | class _OR: |
| 100 | def __init__(self, left, right=None): |
| 101 | self.left = left |
| 102 | self.right = right |
| 103 | |
| 104 | def filled(self): |
| 105 | return self.right is not None |
| 106 | |
| 107 | def __repr__(self): |
| 108 | return 'OR(%r, %r)' % (self.left, self.right) |
| 109 | |
| 110 | def __call__(self): |
| 111 | return self.left() or self.right() |
| 112 | |
| 113 | |
| 114 | class _AND: |
| 115 | def __init__(self, left, right=None): |
| 116 | self.left = left |
| 117 | self.right = right |
| 118 | |
| 119 | def filled(self): |
| 120 | return self.right is not None |
| 121 | |
| 122 | def __repr__(self): |
| 123 | return 'AND(%r, %r)' % (self.left, self.right) |
| 124 | |
| 125 | def __call__(self): |
| 126 | return self.left() and self.right() |
| 127 | |
| 128 | |
| 129 | def interpret(marker, execution_context=None): |
| 130 | """Interpret a marker and return a result depending on environment.""" |
| 131 | marker = marker.strip().encode() |
| 132 | ops = [] |
| 133 | op_starting = True |
| 134 | for token in tokenize(BytesIO(marker).readline): |
| 135 | # Unpack token |
| 136 | toktype, tokval, rowcol, line, logical_line = token |
| 137 | if toktype not in (NAME, OP, STRING, ENDMARKER, ENCODING): |
| 138 | raise SyntaxError('Type not supported "%s"' % tokval) |
| 139 | |
| 140 | if op_starting: |
| 141 | op = _Operation(execution_context) |
| 142 | if len(ops) > 0: |
| 143 | last = ops[-1] |
| 144 | if isinstance(last, (_OR, _AND)) and not last.filled(): |
| 145 | last.right = op |
| 146 | else: |
| 147 | ops.append(op) |
| 148 | else: |
| 149 | ops.append(op) |
| 150 | op_starting = False |
| 151 | else: |
| 152 | op = ops[-1] |
| 153 | |
| 154 | if (toktype == ENDMARKER or |
| 155 | (toktype == NAME and tokval in ('and', 'or'))): |
| 156 | if toktype == NAME and tokval == 'and': |
| 157 | ops.append(_AND(ops.pop())) |
| 158 | elif toktype == NAME and tokval == 'or': |
| 159 | ops.append(_OR(ops.pop())) |
| 160 | op_starting = True |
| 161 | continue |
| 162 | |
| 163 | if isinstance(op, (_OR, _AND)) and op.right is not None: |
| 164 | op = op.right |
| 165 | |
| 166 | if ((toktype in (NAME, STRING) and tokval not in ('in', 'not')) |
| 167 | or (toktype == OP and tokval == '.')): |
| 168 | if op.op is None: |
| 169 | if op.left is None: |
| 170 | op.left = tokval |
| 171 | else: |
| 172 | op.left += tokval |
| 173 | else: |
| 174 | if op.right is None: |
| 175 | op.right = tokval |
| 176 | else: |
| 177 | op.right += tokval |
| 178 | elif toktype == OP or tokval in ('in', 'not'): |
| 179 | if tokval == 'in' and op.op == 'not': |
| 180 | op.op = 'not in' |
| 181 | else: |
| 182 | op.op = tokval |
| 183 | |
| 184 | for op in ops: |
| 185 | if not op(): |
| 186 | return False |
| 187 | return True |