1# -*- coding: utf-8 -*- 2# 3# Copyright (C) 2012-2017 Vinay Sajip. 4# Licensed to the Python Software Foundation under a contributor agreement. 5# See LICENSE.txt and CONTRIBUTORS.txt. 6# 7""" 8Parser for the environment markers micro-language defined in PEP 508. 9""" 10 11# Note: In PEP 345, the micro-language was Python compatible, so the ast 12# module could be used to parse it. However, PEP 508 introduced operators such 13# as ~= and === which aren't in Python, necessitating a different approach. 14 15import os 16import sys 17import platform 18 19from .compat import string_types 20from .util import in_venv, parse_marker 21 22__all__ = ['interpret'] 23 24def _is_literal(o): 25 if not isinstance(o, string_types) or not o: 26 return False 27 return o[0] in '\'"' 28 29class Evaluator(object): 30 """ 31 This class is used to evaluate marker expessions. 32 """ 33 34 operations = { 35 '==': lambda x, y: x == y, 36 '===': lambda x, y: x == y, 37 '~=': lambda x, y: x == y or x > y, 38 '!=': lambda x, y: x != y, 39 '<': lambda x, y: x < y, 40 '<=': lambda x, y: x == y or x < y, 41 '>': lambda x, y: x > y, 42 '>=': lambda x, y: x == y or x > y, 43 'and': lambda x, y: x and y, 44 'or': lambda x, y: x or y, 45 'in': lambda x, y: x in y, 46 'not in': lambda x, y: x not in y, 47 } 48 49 def evaluate(self, expr, context): 50 """ 51 Evaluate a marker expression returned by the :func:`parse_requirement` 52 function in the specified context. 53 """ 54 if isinstance(expr, string_types): 55 if expr[0] in '\'"': 56 result = expr[1:-1] 57 else: 58 if expr not in context: 59 raise SyntaxError('unknown variable: %s' % expr) 60 result = context[expr] 61 else: 62 assert isinstance(expr, dict) 63 op = expr['op'] 64 if op not in self.operations: 65 raise NotImplementedError('op not implemented: %s' % op) 66 elhs = expr['lhs'] 67 erhs = expr['rhs'] 68 if _is_literal(expr['lhs']) and _is_literal(expr['rhs']): 69 raise SyntaxError('invalid comparison: %s %s %s' % (elhs, op, erhs)) 70 71 lhs = self.evaluate(elhs, context) 72 rhs = self.evaluate(erhs, context) 73 result = self.operations[op](lhs, rhs) 74 return result 75 76def default_context(): 77 def format_full_version(info): 78 version = '%s.%s.%s' % (info.major, info.minor, info.micro) 79 kind = info.releaselevel 80 if kind != 'final': 81 version += kind[0] + str(info.serial) 82 return version 83 84 if hasattr(sys, 'implementation'): 85 implementation_version = format_full_version(sys.implementation.version) 86 implementation_name = sys.implementation.name 87 else: 88 implementation_version = '0' 89 implementation_name = '' 90 91 result = { 92 'implementation_name': implementation_name, 93 'implementation_version': implementation_version, 94 'os_name': os.name, 95 'platform_machine': platform.machine(), 96 'platform_python_implementation': platform.python_implementation(), 97 'platform_release': platform.release(), 98 'platform_system': platform.system(), 99 'platform_version': platform.version(), 100 'platform_in_venv': str(in_venv()), 101 'python_full_version': platform.python_version(), 102 'python_version': platform.python_version()[:3], 103 'sys_platform': sys.platform, 104 } 105 return result 106 107DEFAULT_CONTEXT = default_context() 108del default_context 109 110evaluator = Evaluator() 111 112def interpret(marker, execution_context=None): 113 """ 114 Interpret a marker and return a result depending on environment. 115 116 :param marker: The marker to interpret. 117 :type marker: str 118 :param execution_context: The context used for name lookup. 119 :type execution_context: mapping 120 """ 121 try: 122 expr, rest = parse_marker(marker) 123 except Exception as e: 124 raise SyntaxError('Unable to interpret marker syntax: %s: %s' % (marker, e)) 125 if rest and rest[0] != '#': 126 raise SyntaxError('unexpected trailing data in marker: %s: %s' % (marker, rest)) 127 context = dict(DEFAULT_CONTEXT) 128 if execution_context: 129 context.update(execution_context) 130 return evaluator.evaluate(expr, context) 131