OLD | NEW |
(Empty) | |
| 1 # This file is dual licensed under the terms of the Apache License, Version |
| 2 # 2.0, and the BSD License. See the LICENSE file in the root of this repository |
| 3 # for complete details. |
| 4 from __future__ import absolute_import, division, print_function |
| 5 |
| 6 import operator |
| 7 import os |
| 8 import platform |
| 9 import sys |
| 10 |
| 11 from pyparsing import ParseException, ParseResults, stringStart, stringEnd |
| 12 from pyparsing import ZeroOrMore, Group, Forward, QuotedString |
| 13 from pyparsing import Literal as L # noqa |
| 14 |
| 15 from ._compat import string_types |
| 16 from .specifiers import Specifier, InvalidSpecifier |
| 17 |
| 18 |
| 19 __all__ = [ |
| 20 "InvalidMarker", "UndefinedComparison", "UndefinedEnvironmentName", |
| 21 "Marker", "default_environment", |
| 22 ] |
| 23 |
| 24 |
| 25 class InvalidMarker(ValueError): |
| 26 """ |
| 27 An invalid marker was found, users should refer to PEP 508. |
| 28 """ |
| 29 |
| 30 |
| 31 class UndefinedComparison(ValueError): |
| 32 """ |
| 33 An invalid operation was attempted on a value that doesn't support it. |
| 34 """ |
| 35 |
| 36 |
| 37 class UndefinedEnvironmentName(ValueError): |
| 38 """ |
| 39 A name was attempted to be used that does not exist inside of the |
| 40 environment. |
| 41 """ |
| 42 |
| 43 |
| 44 class Node(object): |
| 45 |
| 46 def __init__(self, value): |
| 47 self.value = value |
| 48 |
| 49 def __str__(self): |
| 50 return str(self.value) |
| 51 |
| 52 def __repr__(self): |
| 53 return "<{0}({1!r})>".format(self.__class__.__name__, str(self)) |
| 54 |
| 55 def serialize(self): |
| 56 raise NotImplementedError |
| 57 |
| 58 |
| 59 class Variable(Node): |
| 60 |
| 61 def serialize(self): |
| 62 return str(self) |
| 63 |
| 64 |
| 65 class Value(Node): |
| 66 |
| 67 def serialize(self): |
| 68 return '"{0}"'.format(self) |
| 69 |
| 70 |
| 71 class Op(Node): |
| 72 |
| 73 def serialize(self): |
| 74 return str(self) |
| 75 |
| 76 |
| 77 VARIABLE = ( |
| 78 L("implementation_version") | |
| 79 L("platform_python_implementation") | |
| 80 L("implementation_name") | |
| 81 L("python_full_version") | |
| 82 L("platform_release") | |
| 83 L("platform_version") | |
| 84 L("platform_machine") | |
| 85 L("platform_system") | |
| 86 L("python_version") | |
| 87 L("sys_platform") | |
| 88 L("os_name") | |
| 89 L("os.name") | # PEP-345 |
| 90 L("sys.platform") | # PEP-345 |
| 91 L("platform.version") | # PEP-345 |
| 92 L("platform.machine") | # PEP-345 |
| 93 L("platform.python_implementation") | # PEP-345 |
| 94 L("python_implementation") | # undocumented setuptools legacy |
| 95 L("extra") |
| 96 ) |
| 97 ALIASES = { |
| 98 'os.name': 'os_name', |
| 99 'sys.platform': 'sys_platform', |
| 100 'platform.version': 'platform_version', |
| 101 'platform.machine': 'platform_machine', |
| 102 'platform.python_implementation': 'platform_python_implementation', |
| 103 'python_implementation': 'platform_python_implementation' |
| 104 } |
| 105 VARIABLE.setParseAction(lambda s, l, t: Variable(ALIASES.get(t[0], t[0]))) |
| 106 |
| 107 VERSION_CMP = ( |
| 108 L("===") | |
| 109 L("==") | |
| 110 L(">=") | |
| 111 L("<=") | |
| 112 L("!=") | |
| 113 L("~=") | |
| 114 L(">") | |
| 115 L("<") |
| 116 ) |
| 117 |
| 118 MARKER_OP = VERSION_CMP | L("not in") | L("in") |
| 119 MARKER_OP.setParseAction(lambda s, l, t: Op(t[0])) |
| 120 |
| 121 MARKER_VALUE = QuotedString("'") | QuotedString('"') |
| 122 MARKER_VALUE.setParseAction(lambda s, l, t: Value(t[0])) |
| 123 |
| 124 BOOLOP = L("and") | L("or") |
| 125 |
| 126 MARKER_VAR = VARIABLE | MARKER_VALUE |
| 127 |
| 128 MARKER_ITEM = Group(MARKER_VAR + MARKER_OP + MARKER_VAR) |
| 129 MARKER_ITEM.setParseAction(lambda s, l, t: tuple(t[0])) |
| 130 |
| 131 LPAREN = L("(").suppress() |
| 132 RPAREN = L(")").suppress() |
| 133 |
| 134 MARKER_EXPR = Forward() |
| 135 MARKER_ATOM = MARKER_ITEM | Group(LPAREN + MARKER_EXPR + RPAREN) |
| 136 MARKER_EXPR << MARKER_ATOM + ZeroOrMore(BOOLOP + MARKER_EXPR) |
| 137 |
| 138 MARKER = stringStart + MARKER_EXPR + stringEnd |
| 139 |
| 140 |
| 141 def _coerce_parse_result(results): |
| 142 if isinstance(results, ParseResults): |
| 143 return [_coerce_parse_result(i) for i in results] |
| 144 else: |
| 145 return results |
| 146 |
| 147 |
| 148 def _format_marker(marker, first=True): |
| 149 assert isinstance(marker, (list, tuple, string_types)) |
| 150 |
| 151 # Sometimes we have a structure like [[...]] which is a single item list |
| 152 # where the single item is itself it's own list. In that case we want skip |
| 153 # the rest of this function so that we don't get extraneous () on the |
| 154 # outside. |
| 155 if (isinstance(marker, list) and len(marker) == 1 and |
| 156 isinstance(marker[0], (list, tuple))): |
| 157 return _format_marker(marker[0]) |
| 158 |
| 159 if isinstance(marker, list): |
| 160 inner = (_format_marker(m, first=False) for m in marker) |
| 161 if first: |
| 162 return " ".join(inner) |
| 163 else: |
| 164 return "(" + " ".join(inner) + ")" |
| 165 elif isinstance(marker, tuple): |
| 166 return " ".join([m.serialize() for m in marker]) |
| 167 else: |
| 168 return marker |
| 169 |
| 170 |
| 171 _operators = { |
| 172 "in": lambda lhs, rhs: lhs in rhs, |
| 173 "not in": lambda lhs, rhs: lhs not in rhs, |
| 174 "<": operator.lt, |
| 175 "<=": operator.le, |
| 176 "==": operator.eq, |
| 177 "!=": operator.ne, |
| 178 ">=": operator.ge, |
| 179 ">": operator.gt, |
| 180 } |
| 181 |
| 182 |
| 183 def _eval_op(lhs, op, rhs): |
| 184 try: |
| 185 spec = Specifier("".join([op.serialize(), rhs])) |
| 186 except InvalidSpecifier: |
| 187 pass |
| 188 else: |
| 189 return spec.contains(lhs) |
| 190 |
| 191 oper = _operators.get(op.serialize()) |
| 192 if oper is None: |
| 193 raise UndefinedComparison( |
| 194 "Undefined {0!r} on {1!r} and {2!r}.".format(op, lhs, rhs) |
| 195 ) |
| 196 |
| 197 return oper(lhs, rhs) |
| 198 |
| 199 |
| 200 _undefined = object() |
| 201 |
| 202 |
| 203 def _get_env(environment, name): |
| 204 value = environment.get(name, _undefined) |
| 205 |
| 206 if value is _undefined: |
| 207 raise UndefinedEnvironmentName( |
| 208 "{0!r} does not exist in evaluation environment.".format(name) |
| 209 ) |
| 210 |
| 211 return value |
| 212 |
| 213 |
| 214 def _evaluate_markers(markers, environment): |
| 215 groups = [[]] |
| 216 |
| 217 for marker in markers: |
| 218 assert isinstance(marker, (list, tuple, string_types)) |
| 219 |
| 220 if isinstance(marker, list): |
| 221 groups[-1].append(_evaluate_markers(marker, environment)) |
| 222 elif isinstance(marker, tuple): |
| 223 lhs, op, rhs = marker |
| 224 |
| 225 if isinstance(lhs, Variable): |
| 226 lhs_value = _get_env(environment, lhs.value) |
| 227 rhs_value = rhs.value |
| 228 else: |
| 229 lhs_value = lhs.value |
| 230 rhs_value = _get_env(environment, rhs.value) |
| 231 |
| 232 groups[-1].append(_eval_op(lhs_value, op, rhs_value)) |
| 233 else: |
| 234 assert marker in ["and", "or"] |
| 235 if marker == "or": |
| 236 groups.append([]) |
| 237 |
| 238 return any(all(item) for item in groups) |
| 239 |
| 240 |
| 241 def format_full_version(info): |
| 242 version = '{0.major}.{0.minor}.{0.micro}'.format(info) |
| 243 kind = info.releaselevel |
| 244 if kind != 'final': |
| 245 version += kind[0] + str(info.serial) |
| 246 return version |
| 247 |
| 248 |
| 249 def default_environment(): |
| 250 if hasattr(sys, 'implementation'): |
| 251 iver = format_full_version(sys.implementation.version) |
| 252 implementation_name = sys.implementation.name |
| 253 else: |
| 254 iver = '0' |
| 255 implementation_name = '' |
| 256 |
| 257 return { |
| 258 "implementation_name": implementation_name, |
| 259 "implementation_version": iver, |
| 260 "os_name": os.name, |
| 261 "platform_machine": platform.machine(), |
| 262 "platform_release": platform.release(), |
| 263 "platform_system": platform.system(), |
| 264 "platform_version": platform.version(), |
| 265 "python_full_version": platform.python_version(), |
| 266 "platform_python_implementation": platform.python_implementation(), |
| 267 "python_version": platform.python_version()[:3], |
| 268 "sys_platform": sys.platform, |
| 269 } |
| 270 |
| 271 |
| 272 class Marker(object): |
| 273 |
| 274 def __init__(self, marker): |
| 275 try: |
| 276 self._markers = _coerce_parse_result(MARKER.parseString(marker)) |
| 277 except ParseException as e: |
| 278 err_str = "Invalid marker: {0!r}, parse error at {1!r}".format( |
| 279 marker, marker[e.loc:e.loc + 8]) |
| 280 raise InvalidMarker(err_str) |
| 281 |
| 282 def __str__(self): |
| 283 return _format_marker(self._markers) |
| 284 |
| 285 def __repr__(self): |
| 286 return "<Marker({0!r})>".format(str(self)) |
| 287 |
| 288 def evaluate(self, environment=None): |
| 289 """Evaluate a marker. |
| 290 |
| 291 Return the boolean from evaluating the given marker against the |
| 292 environment. environment is an optional argument to override all or |
| 293 part of the determined environment. |
| 294 |
| 295 The environment is determined from the current Python process. |
| 296 """ |
| 297 current_environment = default_environment() |
| 298 if environment is not None: |
| 299 current_environment.update(environment) |
| 300 |
| 301 return _evaluate_markers(self._markers, current_environment) |
OLD | NEW |