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 18import re 19 20from .compat import python_implementation, urlparse, string_types 21from .util import in_venv, parse_marker 22 23__all__ = ['interpret'] 24 25def _is_literal(o): 26 if not isinstance(o, string_types) or not o: 27 return False 28 return o[0] in '\'"' 29 30class Evaluator(object): 31 """ 32 This class is used to evaluate marker expessions. 33 """ 34 35 operations = { 36 '==': lambda x, y: x == y, 37 '===': lambda x, y: x == y, 38 '~=': lambda x, y: x == y or x > y, 39 '!=': lambda x, y: x != y, 40 '<': lambda x, y: x < y, 41 '<=': lambda x, y: x == y or x < y, 42 '>': lambda x, y: x > y, 43 '>=': lambda x, y: x == y or x > y, 44 'and': lambda x, y: x and y, 45 'or': lambda x, y: x or y, 46 'in': lambda x, y: x in y, 47 'not in': lambda x, y: x not in y, 48 } 49 50 def evaluate(self, expr, context): 51 """ 52 Evaluate a marker expression returned by the :func:`parse_requirement` 53 function in the specified context. 54 """ 55 if isinstance(expr, string_types): 56 if expr[0] in '\'"': 57 result = expr[1:-1] 58 else: 59 if expr not in context: 60 raise SyntaxError('unknown variable: %s' % expr) 61 result = context[expr] 62 else: 63 assert isinstance(expr, dict) 64 op = expr['op'] 65 if op not in self.operations: 66 raise NotImplementedError('op not implemented: %s' % op) 67 elhs = expr['lhs'] 68 erhs = expr['rhs'] 69 if _is_literal(expr['lhs']) and _is_literal(expr['rhs']): 70 raise SyntaxError('invalid comparison: %s %s %s' % (elhs, op, erhs)) 71 72 lhs = self.evaluate(elhs, context) 73 rhs = self.evaluate(erhs, context) 74 result = self.operations[op](lhs, rhs) 75 return result 76 77def default_context(): 78 def format_full_version(info): 79 version = '%s.%s.%s' % (info.major, info.minor, info.micro) 80 kind = info.releaselevel 81 if kind != 'final': 82 version += kind[0] + str(info.serial) 83 return version 84 85 if hasattr(sys, 'implementation'): 86 implementation_version = format_full_version(sys.implementation.version) 87 implementation_name = sys.implementation.name 88 else: 89 implementation_version = '0' 90 implementation_name = '' 91 92 result = { 93 'implementation_name': implementation_name, 94 'implementation_version': implementation_version, 95 'os_name': os.name, 96 'platform_machine': platform.machine(), 97 'platform_python_implementation': platform.python_implementation(), 98 'platform_release': platform.release(), 99 'platform_system': platform.system(), 100 'platform_version': platform.version(), 101 'platform_in_venv': str(in_venv()), 102 'python_full_version': platform.python_version(), 103 'python_version': platform.python_version()[:3], 104 'sys_platform': sys.platform, 105 } 106 return result 107 108DEFAULT_CONTEXT = default_context() 109del default_context 110 111evaluator = Evaluator() 112 113def interpret(marker, execution_context=None): 114 """ 115 Interpret a marker and return a result depending on environment. 116 117 :param marker: The marker to interpret. 118 :type marker: str 119 :param execution_context: The context used for name lookup. 120 :type execution_context: mapping 121 """ 122 try: 123 expr, rest = parse_marker(marker) 124 except Exception as e: 125 raise SyntaxError('Unable to interpret marker syntax: %s: %s' % (marker, e)) 126 if rest and rest[0] != '#': 127 raise SyntaxError('unexpected trailing data in marker: %s: %s' % (marker, rest)) 128 context = dict(DEFAULT_CONTEXT) 129 if execution_context: 130 context.update(execution_context) 131 return evaluator.evaluate(expr, context) 132