1
# Copyright (C) 2009 www.stani.be
3
# This program is free software: you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation, either version 3 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program. If not, see http://www.gnu.org/licenses/
18
#import rpdb2;rpdb2.start_embedded_debugger('x')
24
'int': ['abs', 'int', 'min', 'max', 'pow', 'sum'],
25
'str': ['chr', 'lower', 'str', 'title', 'upper'],
26
'bool': ['True', 'False'],
27
'datetime': ['day', 'hour', 'microsecond', 'minute', 'month',
28
'monthname', 'second', 'weekday', 'weekdayname', 'year'],
29
'rational': ['denominator', 'numerator'],
31
SAFE['all'] = reduce(operator.add, SAFE.values())
34
"""Todo: alleen format ### moet vervangen worden, daarna gewoon
35
eval met locals (incl indices) en globals.
37
<x>_<y> wordt '%s_%s'(eval(x),eval(y)) '"""
39
RE_EXPR = re.compile('<([^<>]+?)>', re.UNICODE)
40
RE_FORMAT = re.compile('#+')
41
RE_VAR = re.compile('(?P<var>[A-Za-z]\w*)(?P<attr>([.]\w(\w|[.])+)?)',
45
class UnsafeError(Exception):
49
def _format_int(match):
50
"""Converts a ``####`` string into a formating string.
52
Helper function for :func:`format_expr`.
54
:param match: match for a ``##``-string
55
:type match: regular expression match
56
:returns: interpolation format
59
>>> f = _format_int(RE_FORMAT.search('####'))
65
return '"%%0%dd"%%' % len(match.group(0))
69
"""Returns an expression with ``####`` in a pure python expression
70
which can be evaluated.
75
>>> f = format_expr('###(5+1)')
81
return RE_FORMAT.sub(_format_int, s)
84
def compile_expr(meta_expr, _globals=None, _locals=None, validate=None,
85
preprocess=lambda x: x, safe=True):
86
"""If safe is a list, a restricted evaluation will be executed.
87
Otherwise if safe is None, a unrestriced eval will be executed.
89
:param meta_expr: meta-expression with <subexpressions>
90
:type meta_expr: string
91
:param _globals: globals
93
:param _locals: locals
95
:param safe: safe names which will be accepted by the compiler
96
:type safe: list or None
97
:param preprocess: preprocess expression (e.g. for ## formatting)
98
:type preprocess: callable
100
>>> compile_expr('<1+1>_<abs(2-3)>', safe=False)
102
>>> compile_expr('<###(index+1)>', _locals={'index':1},
103
... preprocess=format_expr, safe=False)
113
def compile_sub_expr(expr):
114
return unicode(eval_safe(preprocess(expr.group(1)),
115
_globals, _locals, validate))
119
def compile_sub_expr(expr):
120
return unicode(eval(preprocess(expr.group(1)),
123
return RE_EXPR.sub(compile_sub_expr, meta_expr)
126
def assert_safe_expr(meta_expr, _globals=None, _locals=None, validate=None,
127
preprocess=lambda x: x):
128
for expr in RE_EXPR.finditer(meta_expr):
129
assert_safe(preprocess(expr.group(1)), _globals, _locals, validate)
132
def assert_safe(expr, _globals=None, _locals=None, validate=None):
137
code = compile(expr, '<%s>' % expr, 'eval')
140
not_allowed = validate(code.co_names, _globals, _locals)
142
not_allowed = code.co_names
145
_('The following name(s) are invalid: ') + \
146
', '.join([_(x) for x in not_allowed]))
147
return code, _globals, _locals
150
def eval_safe(expr, _globals=None, _locals=None, validate=None):
151
"""Safely evaluate an expression. It will raise a ``ValueError`` if
152
non validated names are used.
154
:param expr: expression
161
... eval_safe('"lowercase".upper()')
162
... except UnsafeError, error:
164
The following name(s) are invalid: upper
170
return eval(*assert_safe(expr, _globals, _locals, validate))
173
def eval_restricted(s, _globals=None, _locals=None, allowed=SAFE['all'][:]):
174
"""Evaluate an expression while allowing a restricted set of names.
176
:param allowed: allowed names
177
:type allowed: list of string
180
>>> eval_restricted('max(a, a+b)', _globals={'a':0, 'b':2},
181
... _locals={'a':1}, allowed=['max'])
184
... eval_restricted('a+b+c', _globals={'a':0, 'b':2}, _locals={'a':1})
185
... except UnsafeError, error:
187
The following name(s) are invalid: c
193
allowed += reduce(operator.add, [v.keys() for v in (_locals, _globals)])
195
def validate(names, _globals, _locals):
196
return set(names).difference(allowed)
198
return eval_safe(s, _globals, _locals, validate)
201
def extend_vars(vars, s):
202
"""Extend ``vars`` with new unique variables from ``s``.
204
:param vars: collection of previous variables
205
:type vars: list of string
206
:param s: multiple expressions
210
>>> extend_vars(vars, '<a1>_<foo>_<world>_<###index>')
212
['a1', 'foo', 'world', 'index']
214
for expr in RE_EXPR.findall(s):
216
for match in RE_VAR.finditer(expr):
217
var = match.group('var')