1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
|
"""Parser for the environment markers micro-language defined in PEP 345."""
import sys
import platform
import os
from tokenize import tokenize, NAME, OP, STRING, ENDMARKER, ENCODING
from io import BytesIO
__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,
'python_version': sys.version[:3],
'python_full_version': sys.version.split(' ', 1)[0],
'os.name': os.name,
'platform.version': platform.version(),
'platform.machine': platform.machine(),
'platform.python_implementation': platform.python_implementation()}
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
|