blob: 4bbac7eca729b08dbb2133c91e8839a57e0fae5f [file] [log] [blame]
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001"""Parser for the environment markers micro-language defined in PEP 345."""
2
3import sys
4import platform
5import os
6
7from tokenize import tokenize, NAME, OP, STRING, ENDMARKER, ENCODING
8from io import BytesIO
9
10__all__ = ['interpret']
11
12
13# allowed operators
14_OPERATORS = {'==': lambda x, y: x == y,
15 '!=': lambda x, y: x != y,
16 '>': lambda x, y: x > y,
17 '>=': lambda x, y: x >= y,
18 '<': lambda x, y: x < y,
19 '<=': lambda x, y: x <= y,
20 'in': lambda x, y: x in y,
21 'not in': lambda x, y: x not in y}
22
23
24def _operate(operation, x, y):
25 return _OPERATORS[operation](x, y)
26
27
28# restricted set of variables
29_VARS = {'sys.platform': sys.platform,
30 'python_version': sys.version[:3],
31 'python_full_version': sys.version.split(' ', 1)[0],
32 'os.name': os.name,
33 'platform.version': platform.version(),
34 'platform.machine': platform.machine(),
35 'platform.python_implementation': platform.python_implementation()}
36
37
38class _Operation:
39
40 def __init__(self, execution_context=None):
41 self.left = None
42 self.op = None
43 self.right = None
44 if execution_context is None:
45 execution_context = {}
46 self.execution_context = execution_context
47
48 def _get_var(self, name):
49 if name in self.execution_context:
50 return self.execution_context[name]
51 return _VARS[name]
52
53 def __repr__(self):
54 return '%s %s %s' % (self.left, self.op, self.right)
55
56 def _is_string(self, value):
57 if value is None or len(value) < 2:
58 return False
59 for delimiter in '"\'':
60 if value[0] == value[-1] == delimiter:
61 return True
62 return False
63
64 def _is_name(self, value):
65 return value in _VARS
66
67 def _convert(self, value):
68 if value in _VARS:
69 return self._get_var(value)
70 return value.strip('"\'')
71
72 def _check_name(self, value):
73 if value not in _VARS:
74 raise NameError(value)
75
76 def _nonsense_op(self):
77 msg = 'This operation is not supported : "%s"' % self
78 raise SyntaxError(msg)
79
80 def __call__(self):
81 # make sure we do something useful
82 if self._is_string(self.left):
83 if self._is_string(self.right):
84 self._nonsense_op()
85 self._check_name(self.right)
86 else:
87 if not self._is_string(self.right):
88 self._nonsense_op()
89 self._check_name(self.left)
90
91 if self.op not in _OPERATORS:
92 raise TypeError('Operator not supported "%s"' % self.op)
93
94 left = self._convert(self.left)
95 right = self._convert(self.right)
96 return _operate(self.op, left, right)
97
98
99class _OR:
100 def __init__(self, left, right=None):
101 self.left = left
102 self.right = right
103
104 def filled(self):
105 return self.right is not None
106
107 def __repr__(self):
108 return 'OR(%r, %r)' % (self.left, self.right)
109
110 def __call__(self):
111 return self.left() or self.right()
112
113
114class _AND:
115 def __init__(self, left, right=None):
116 self.left = left
117 self.right = right
118
119 def filled(self):
120 return self.right is not None
121
122 def __repr__(self):
123 return 'AND(%r, %r)' % (self.left, self.right)
124
125 def __call__(self):
126 return self.left() and self.right()
127
128
129def interpret(marker, execution_context=None):
130 """Interpret a marker and return a result depending on environment."""
131 marker = marker.strip().encode()
132 ops = []
133 op_starting = True
134 for token in tokenize(BytesIO(marker).readline):
135 # Unpack token
136 toktype, tokval, rowcol, line, logical_line = token
137 if toktype not in (NAME, OP, STRING, ENDMARKER, ENCODING):
138 raise SyntaxError('Type not supported "%s"' % tokval)
139
140 if op_starting:
141 op = _Operation(execution_context)
142 if len(ops) > 0:
143 last = ops[-1]
144 if isinstance(last, (_OR, _AND)) and not last.filled():
145 last.right = op
146 else:
147 ops.append(op)
148 else:
149 ops.append(op)
150 op_starting = False
151 else:
152 op = ops[-1]
153
154 if (toktype == ENDMARKER or
155 (toktype == NAME and tokval in ('and', 'or'))):
156 if toktype == NAME and tokval == 'and':
157 ops.append(_AND(ops.pop()))
158 elif toktype == NAME and tokval == 'or':
159 ops.append(_OR(ops.pop()))
160 op_starting = True
161 continue
162
163 if isinstance(op, (_OR, _AND)) and op.right is not None:
164 op = op.right
165
166 if ((toktype in (NAME, STRING) and tokval not in ('in', 'not'))
167 or (toktype == OP and tokval == '.')):
168 if op.op is None:
169 if op.left is None:
170 op.left = tokval
171 else:
172 op.left += tokval
173 else:
174 if op.right is None:
175 op.right = tokval
176 else:
177 op.right += tokval
178 elif toktype == OP or tokval in ('in', 'not'):
179 if tokval == 'in' and op.op == 'not':
180 op.op = 'not in'
181 else:
182 op.op = tokval
183
184 for op in ops:
185 if not op():
186 return False
187 return True