| 
									
										
										
										
											2011-05-19 13:07:25 +02:00
										 |  |  | """Parser for the environment markers micro-language defined in PEP 345.""" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-02-10 05:20:53 +01:00
										 |  |  | import os | 
					
						
							| 
									
										
										
										
											2011-05-19 13:07:25 +02:00
										 |  |  | import sys | 
					
						
							|  |  |  | import platform | 
					
						
							|  |  |  | from io import BytesIO | 
					
						
							| 
									
										
										
										
											2012-02-10 05:20:53 +01:00
										 |  |  | from tokenize import tokenize, NAME, OP, STRING, ENDMARKER, ENCODING | 
					
						
							| 
									
										
										
										
											2011-05-19 13:07:25 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | __all__ = ['interpret'] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # allowed operators | 
					
						
							|  |  |  | _OPERATORS = {'==': lambda x, y: x == y, | 
					
						
							|  |  |  |               '!=': lambda x, y: x != y, | 
					
						
							|  |  |  |               '>': lambda x, y: x > y, | 
					
						
							|  |  |  |               '>=': lambda x, y: x >= y, | 
					
						
							|  |  |  |               '<': lambda x, y: x < y, | 
					
						
							|  |  |  |               '<=': lambda x, y: x <= y, | 
					
						
							|  |  |  |               'in': lambda x, y: x in y, | 
					
						
							|  |  |  |               'not in': lambda x, y: x not in y} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def _operate(operation, x, y): | 
					
						
							|  |  |  |     return _OPERATORS[operation](x, y) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # restricted set of variables | 
					
						
							|  |  |  | _VARS = {'sys.platform': sys.platform, | 
					
						
							| 
									
										
										
										
											2012-02-10 05:20:53 +01:00
										 |  |  |          'python_version': '%s.%s' % sys.version_info[:2], | 
					
						
							|  |  |  |          # FIXME parsing sys.platform is not reliable, but there is no other | 
					
						
							|  |  |  |          # way to get e.g. 2.7.2+, and the PEP is defined with sys.version | 
					
						
							| 
									
										
										
										
											2011-05-19 13:07:25 +02:00
										 |  |  |          'python_full_version': sys.version.split(' ', 1)[0], | 
					
						
							|  |  |  |          'os.name': os.name, | 
					
						
							|  |  |  |          'platform.version': platform.version(), | 
					
						
							|  |  |  |          'platform.machine': platform.machine(), | 
					
						
							| 
									
										
										
										
											2012-02-10 05:20:53 +01:00
										 |  |  |          'platform.python_implementation': platform.python_implementation(), | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2011-05-19 13:07:25 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class _Operation: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, execution_context=None): | 
					
						
							|  |  |  |         self.left = None | 
					
						
							|  |  |  |         self.op = None | 
					
						
							|  |  |  |         self.right = None | 
					
						
							|  |  |  |         if execution_context is None: | 
					
						
							|  |  |  |             execution_context = {} | 
					
						
							|  |  |  |         self.execution_context = execution_context | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _get_var(self, name): | 
					
						
							|  |  |  |         if name in self.execution_context: | 
					
						
							|  |  |  |             return self.execution_context[name] | 
					
						
							|  |  |  |         return _VARS[name] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __repr__(self): | 
					
						
							|  |  |  |         return '%s %s %s' % (self.left, self.op, self.right) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _is_string(self, value): | 
					
						
							|  |  |  |         if value is None or len(value) < 2: | 
					
						
							|  |  |  |             return False | 
					
						
							|  |  |  |         for delimiter in '"\'': | 
					
						
							|  |  |  |             if value[0] == value[-1] == delimiter: | 
					
						
							|  |  |  |                 return True | 
					
						
							|  |  |  |         return False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _is_name(self, value): | 
					
						
							|  |  |  |         return value in _VARS | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _convert(self, value): | 
					
						
							|  |  |  |         if value in _VARS: | 
					
						
							|  |  |  |             return self._get_var(value) | 
					
						
							|  |  |  |         return value.strip('"\'') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _check_name(self, value): | 
					
						
							|  |  |  |         if value not in _VARS: | 
					
						
							|  |  |  |             raise NameError(value) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _nonsense_op(self): | 
					
						
							|  |  |  |         msg = 'This operation is not supported : "%s"' % self | 
					
						
							|  |  |  |         raise SyntaxError(msg) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __call__(self): | 
					
						
							|  |  |  |         # make sure we do something useful | 
					
						
							|  |  |  |         if self._is_string(self.left): | 
					
						
							|  |  |  |             if self._is_string(self.right): | 
					
						
							|  |  |  |                 self._nonsense_op() | 
					
						
							|  |  |  |             self._check_name(self.right) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             if not self._is_string(self.right): | 
					
						
							|  |  |  |                 self._nonsense_op() | 
					
						
							|  |  |  |             self._check_name(self.left) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if self.op not in _OPERATORS: | 
					
						
							|  |  |  |             raise TypeError('Operator not supported "%s"' % self.op) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         left = self._convert(self.left) | 
					
						
							|  |  |  |         right = self._convert(self.right) | 
					
						
							|  |  |  |         return _operate(self.op, left, right) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class _OR: | 
					
						
							|  |  |  |     def __init__(self, left, right=None): | 
					
						
							|  |  |  |         self.left = left | 
					
						
							|  |  |  |         self.right = right | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def filled(self): | 
					
						
							|  |  |  |         return self.right is not None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __repr__(self): | 
					
						
							|  |  |  |         return 'OR(%r, %r)' % (self.left, self.right) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __call__(self): | 
					
						
							|  |  |  |         return self.left() or self.right() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class _AND: | 
					
						
							|  |  |  |     def __init__(self, left, right=None): | 
					
						
							|  |  |  |         self.left = left | 
					
						
							|  |  |  |         self.right = right | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def filled(self): | 
					
						
							|  |  |  |         return self.right is not None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __repr__(self): | 
					
						
							|  |  |  |         return 'AND(%r, %r)' % (self.left, self.right) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __call__(self): | 
					
						
							|  |  |  |         return self.left() and self.right() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def interpret(marker, execution_context=None): | 
					
						
							|  |  |  |     """Interpret a marker and return a result depending on environment.""" | 
					
						
							|  |  |  |     marker = marker.strip().encode() | 
					
						
							|  |  |  |     ops = [] | 
					
						
							|  |  |  |     op_starting = True | 
					
						
							|  |  |  |     for token in tokenize(BytesIO(marker).readline): | 
					
						
							|  |  |  |         # Unpack token | 
					
						
							|  |  |  |         toktype, tokval, rowcol, line, logical_line = token | 
					
						
							|  |  |  |         if toktype not in (NAME, OP, STRING, ENDMARKER, ENCODING): | 
					
						
							|  |  |  |             raise SyntaxError('Type not supported "%s"' % tokval) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if op_starting: | 
					
						
							|  |  |  |             op = _Operation(execution_context) | 
					
						
							|  |  |  |             if len(ops) > 0: | 
					
						
							|  |  |  |                 last = ops[-1] | 
					
						
							|  |  |  |                 if isinstance(last, (_OR, _AND)) and not last.filled(): | 
					
						
							|  |  |  |                     last.right = op | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     ops.append(op) | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 ops.append(op) | 
					
						
							|  |  |  |             op_starting = False | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             op = ops[-1] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (toktype == ENDMARKER or | 
					
						
							|  |  |  |             (toktype == NAME and tokval in ('and', 'or'))): | 
					
						
							|  |  |  |             if toktype == NAME and tokval == 'and': | 
					
						
							|  |  |  |                 ops.append(_AND(ops.pop())) | 
					
						
							|  |  |  |             elif toktype == NAME and tokval == 'or': | 
					
						
							|  |  |  |                 ops.append(_OR(ops.pop())) | 
					
						
							|  |  |  |             op_starting = True | 
					
						
							|  |  |  |             continue | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if isinstance(op, (_OR, _AND)) and op.right is not None: | 
					
						
							|  |  |  |             op = op.right | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if ((toktype in (NAME, STRING) and tokval not in ('in', 'not')) | 
					
						
							|  |  |  |             or (toktype == OP and tokval == '.')): | 
					
						
							|  |  |  |             if op.op is None: | 
					
						
							|  |  |  |                 if op.left is None: | 
					
						
							|  |  |  |                     op.left = tokval | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     op.left += tokval | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 if op.right is None: | 
					
						
							|  |  |  |                     op.right = tokval | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     op.right += tokval | 
					
						
							|  |  |  |         elif toktype == OP or tokval in ('in', 'not'): | 
					
						
							|  |  |  |             if tokval == 'in' and op.op == 'not': | 
					
						
							|  |  |  |                 op.op = 'not in' | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 op.op = tokval | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for op in ops: | 
					
						
							|  |  |  |         if not op(): | 
					
						
							|  |  |  |             return False | 
					
						
							|  |  |  |     return True |