|
| 1 | +# -*- coding: utf-8 -*- |
| 2 | +"""Interpret PEP 345 environment markers. |
| 3 | +
|
| 4 | +EXPR [in|==|!=|not in] EXPR [or|and] ... |
| 5 | +
|
| 6 | +where EXPR belongs to any of those: |
| 7 | +
|
| 8 | + python_version = '%s.%s' % (sys.version_info[0], sys.version_info[1]) |
| 9 | + python_full_version = sys.version.split()[0] |
| 10 | + os.name = os.name |
| 11 | + sys.platform = sys.platform |
| 12 | + platform.version = platform.version() |
| 13 | + platform.machine = platform.machine() |
| 14 | + platform.python_implementation = platform.python_implementation() |
| 15 | + a free string, like '2.6', or 'win32' |
| 16 | +""" |
| 17 | + |
| 18 | +__all__ = ['default_environment', 'compile', 'interpret'] |
| 19 | + |
| 20 | +import ast |
| 21 | +import os |
| 22 | +import platform |
| 23 | +import sys |
| 24 | +import weakref |
| 25 | + |
| 26 | +_builtin_compile = compile |
| 27 | + |
| 28 | +try: |
| 29 | + from platform import python_implementation |
| 30 | +except ImportError: |
| 31 | + if os.name == "java": |
| 32 | + # Jython 2.5 has ast module, but not platform.python_implementation() function. |
| 33 | + def python_implementation(): |
| 34 | + return "Jython" |
| 35 | + else: |
| 36 | + raise |
| 37 | + |
| 38 | + |
| 39 | +# restricted set of variables |
| 40 | +_VARS = {'sys.platform': sys.platform, |
| 41 | + 'python_version': '%s.%s' % sys.version_info[:2], |
| 42 | + # FIXME parsing sys.platform is not reliable, but there is no other |
| 43 | + # way to get e.g. 2.7.2+, and the PEP is defined with sys.version |
| 44 | + 'python_full_version': sys.version.split(' ', 1)[0], |
| 45 | + 'os.name': os.name, |
| 46 | + 'platform.version': platform.version(), |
| 47 | + 'platform.machine': platform.machine(), |
| 48 | + 'platform.python_implementation': python_implementation(), |
| 49 | + 'extra': None # wheel extension |
| 50 | + } |
| 51 | + |
| 52 | +for var in list(_VARS.keys()): |
| 53 | + if '.' in var: |
| 54 | + _VARS[var.replace('.', '_')] = _VARS[var] |
| 55 | + |
| 56 | +def default_environment(): |
| 57 | + """Return copy of default PEP 385 globals dictionary.""" |
| 58 | + return dict(_VARS) |
| 59 | + |
| 60 | +class ASTWhitelist(ast.NodeTransformer): |
| 61 | + def __init__(self, statement): |
| 62 | + self.statement = statement # for error messages |
| 63 | + |
| 64 | + ALLOWED = (ast.Compare, ast.BoolOp, ast.Attribute, ast.Name, ast.Load, ast.Str) |
| 65 | + # Bool operations |
| 66 | + ALLOWED += (ast.And, ast.Or) |
| 67 | + # Comparison operations |
| 68 | + ALLOWED += (ast.Eq, ast.Gt, ast.GtE, ast.In, ast.Is, ast.IsNot, ast.Lt, ast.LtE, ast.NotEq, ast.NotIn) |
| 69 | + |
| 70 | + def visit(self, node): |
| 71 | + """Ensure statement only contains allowed nodes.""" |
| 72 | + if not isinstance(node, self.ALLOWED): |
| 73 | + raise SyntaxError('Not allowed in environment markers.\n%s\n%s' % |
| 74 | + (self.statement, |
| 75 | + (' ' * node.col_offset) + '^')) |
| 76 | + return ast.NodeTransformer.visit(self, node) |
| 77 | + |
| 78 | + def visit_Attribute(self, node): |
| 79 | + """Flatten one level of attribute access.""" |
| 80 | + new_node = ast.Name("%s.%s" % (node.value.id, node.attr), node.ctx) |
| 81 | + return ast.copy_location(new_node, node) |
| 82 | + |
| 83 | +def parse_marker(marker): |
| 84 | + tree = ast.parse(marker, mode='eval') |
| 85 | + new_tree = ASTWhitelist(marker).generic_visit(tree) |
| 86 | + return new_tree |
| 87 | + |
| 88 | +def compile_marker(parsed_marker): |
| 89 | + return _builtin_compile(parsed_marker, '<environment marker>', 'eval', |
| 90 | + dont_inherit=True) |
| 91 | + |
| 92 | +_cache = weakref.WeakValueDictionary() |
| 93 | + |
| 94 | +def compile(marker): |
| 95 | + """Return compiled marker as a function accepting an environment dict.""" |
| 96 | + try: |
| 97 | + return _cache[marker] |
| 98 | + except KeyError: |
| 99 | + pass |
| 100 | + if not marker.strip(): |
| 101 | + def marker_fn(environment=None, override=None): |
| 102 | + """""" |
| 103 | + return True |
| 104 | + else: |
| 105 | + compiled_marker = compile_marker(parse_marker(marker)) |
| 106 | + def marker_fn(environment=None, override=None): |
| 107 | + """override updates environment""" |
| 108 | + if override is None: |
| 109 | + override = {} |
| 110 | + if environment is None: |
| 111 | + environment = default_environment() |
| 112 | + environment.update(override) |
| 113 | + return eval(compiled_marker, environment) |
| 114 | + marker_fn.__doc__ = marker |
| 115 | + _cache[marker] = marker_fn |
| 116 | + return _cache[marker] |
| 117 | + |
| 118 | +def interpret(marker, environment=None): |
| 119 | + return compile(marker)(environment) |
0 commit comments