~osomon/phatch/extract-all-metadata

« back to all changes in this revision

Viewing changes to phatch/lib/safe.py

  • Committer: spe.stani.be at gmail
  • Date: 2010-03-13 01:39:24 UTC
  • mfrom: (1542.1.33 context)
  • Revision ID: spe.stani.be@gmail.com-20100313013924-x46mqp2wd5c81dt4
merge with context branch

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2009 www.stani.be
2
 
#
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.
7
 
#
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.
12
 
#
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/
15
 
 
16
 
# Follows PEP8
17
 
 
18
 
#import rpdb2;rpdb2.start_embedded_debugger('x')
19
 
 
20
 
import operator
21
 
import re
22
 
 
23
 
SAFE = {
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'],
30
 
}
31
 
SAFE['all'] = reduce(operator.add, SAFE.values())
32
 
 
33
 
 
34
 
"""Todo: alleen format ### moet vervangen worden, daarna gewoon
35
 
eval met locals (incl indices) en globals.
36
 
 
37
 
<x>_<y> wordt '%s_%s'(eval(x),eval(y)) '"""
38
 
 
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|[.])+)?)',
42
 
    re.UNICODE)
43
 
 
44
 
 
45
 
class UnsafeError(Exception):
46
 
    pass
47
 
 
48
 
 
49
 
def _format_int(match):
50
 
    """Converts a ``####`` string into a formating string.
51
 
 
52
 
    Helper function for :func:`format_expr`.
53
 
 
54
 
    :param match: match for a ``##``-string
55
 
    :type match: regular expression match
56
 
    :returns: interpolation format
57
 
    :rtype: string
58
 
 
59
 
    >>> f = _format_int(RE_FORMAT.search('####'))
60
 
    >>> f
61
 
    '"%04d"%'
62
 
    >>> eval(f + '5')
63
 
    '0005'
64
 
    """
65
 
    return '"%%0%dd"%%' % len(match.group(0))
66
 
 
67
 
 
68
 
def format_expr(s):
69
 
    """Returns an expression with ``####`` in a pure python expression
70
 
    which can be evaluated.
71
 
 
72
 
    :param s: expression
73
 
    :type s: expression
74
 
 
75
 
    >>> f = format_expr('###(5+1)')
76
 
    >>> f
77
 
    '"%03d"%(5+1)'
78
 
    >>> eval(f)
79
 
    '006'
80
 
    """
81
 
    return RE_FORMAT.sub(_format_int, s)
82
 
 
83
 
 
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.
88
 
 
89
 
    :param meta_expr: meta-expression with <subexpressions>
90
 
    :type meta_expr: string
91
 
    :param _globals: globals
92
 
    :type _globals: dict
93
 
    :param _locals: locals
94
 
    :type _locals: dict
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
99
 
 
100
 
    >>> compile_expr('<1+1>_<abs(2-3)>', safe=False)
101
 
    u'2_1'
102
 
    >>> compile_expr('<###(index+1)>', _locals={'index':1},
103
 
    ...     preprocess=format_expr, safe=False)
104
 
    u'002'
105
 
    """
106
 
    if _locals is None:
107
 
        _locals = {}
108
 
    if _globals is None:
109
 
        _globals = {}
110
 
 
111
 
    if safe:
112
 
 
113
 
        def compile_sub_expr(expr):
114
 
            return unicode(eval_safe(preprocess(expr.group(1)),
115
 
                _globals, _locals, validate))
116
 
 
117
 
    else:
118
 
 
119
 
        def compile_sub_expr(expr):
120
 
            return unicode(eval(preprocess(expr.group(1)),
121
 
                _globals, _locals))
122
 
 
123
 
    return RE_EXPR.sub(compile_sub_expr, meta_expr)
124
 
 
125
 
 
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)
130
 
 
131
 
 
132
 
def assert_safe(expr, _globals=None, _locals=None, validate=None):
133
 
    if _locals is None:
134
 
        _locals = {}
135
 
    if _globals is None:
136
 
        _globals = {}
137
 
    code = compile(expr, '<%s>' % expr, 'eval')
138
 
    if code.co_names:
139
 
        if validate:
140
 
            not_allowed = validate(code.co_names, _globals, _locals)
141
 
        else:
142
 
            not_allowed = code.co_names
143
 
        if not_allowed:
144
 
            raise UnsafeError(
145
 
                _('The following name(s) are invalid: ') + \
146
 
                ', '.join([_(x) for x in not_allowed]))
147
 
    return code, _globals, _locals
148
 
 
149
 
 
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.
153
 
 
154
 
    :param expr: expression
155
 
    :type expr: string
156
 
    :returns: result
157
 
 
158
 
    >>> eval_safe('1+1')
159
 
    2
160
 
    >>> try:
161
 
    ...     eval_safe('"lowercase".upper()')
162
 
    ... except UnsafeError, error:
163
 
    ...     print(error)
164
 
    The following name(s) are invalid: upper
165
 
    """
166
 
    if _locals is None:
167
 
        _locals = {}
168
 
    if _globals is None:
169
 
        _globals = {}
170
 
    return eval(*assert_safe(expr, _globals, _locals, validate))
171
 
 
172
 
 
173
 
def eval_restricted(s, _globals=None, _locals=None, allowed=SAFE['all'][:]):
174
 
    """Evaluate an expression while allowing a restricted set of names.
175
 
 
176
 
    :param allowed: allowed names
177
 
    :type allowed: list of string
178
 
    :returns: result
179
 
 
180
 
    >>> eval_restricted('max(a, a+b)', _globals={'a':0, 'b':2},
181
 
    ... _locals={'a':1}, allowed=['max'])
182
 
    3
183
 
    >>> try:
184
 
    ...     eval_restricted('a+b+c', _globals={'a':0, 'b':2}, _locals={'a':1})
185
 
    ... except UnsafeError, error:
186
 
    ...     print(error)
187
 
    The following name(s) are invalid: c
188
 
    """
189
 
    if _locals is None:
190
 
        _locals = {}
191
 
    if _globals is None:
192
 
        _globals = {}
193
 
    allowed += reduce(operator.add, [v.keys() for v in (_locals, _globals)])
194
 
 
195
 
    def validate(names, _globals, _locals):
196
 
        return set(names).difference(allowed)
197
 
 
198
 
    return eval_safe(s, _globals, _locals, validate)
199
 
 
200
 
 
201
 
def extend_vars(vars, s):
202
 
    """Extend ``vars`` with new unique variables from ``s``.
203
 
 
204
 
    :param vars: collection of previous variables
205
 
    :type vars: list of string
206
 
    :param s: multiple expressions
207
 
    :type s: string
208
 
 
209
 
    >>> vars = ['a1']
210
 
    >>> extend_vars(vars, '<a1>_<foo>_<world>_<###index>')
211
 
    >>> vars
212
 
    ['a1', 'foo', 'world', 'index']
213
 
    """
214
 
    for expr in RE_EXPR.findall(s):
215
 
        #locate <expr>
216
 
        for match in RE_VAR.finditer(expr):
217
 
            var = match.group('var')
218
 
            if not var in vars:
219
 
                vars.append(var)