add logging module, use logging in cli.rename, cli uses decorator to parse options...
[audiomangler.git] / audiomangler / expression.py
blob3907a42bb74198a79872c09f830c69ce7bda74a2
1 # -*- coding: utf-8 -*-
2 ###########################################################################
3 # Copyright (C) 2008 by Andrew Mahone
4 # <andrew.mahone@gmail.com>
6 # Copyright: See COPYING file that comes with this distribution
8 ###########################################################################
9 from __future__ import absolute_import
10 import os.path
11 import re
12 import codecs
13 import encodings.punycode
14 from RestrictedPython.RCompile import RExpression
15 from RestrictedPython.MutatingWalker import walk
16 from RestrictedPython.Guards import safe_builtins as eval_builtins
17 from string import maketrans
18 from compiler import ast
19 from audiomangler import Config
21 breakre = re.compile("\(|\)|\$\$|\$(?P<nosan>/)?(?P<label>[a-z]+)?(?P<paren>\()?|(?P<raw>[rR])?(?P<quote>'|\")|\\\\.")
22 pathseptrans = unicode(maketrans('/','_')[:48])
23 pathtrans = unicode(maketrans(r'/\[]?=+<>;",*|', os.path.sep + '_' * 13)[:125])
25 eval_builtins = eval_builtins.copy()
26 eval_builtins.update(filter=filter, map=map, max=max, min=min, reduce=reduce, reversed=reversed, slice=slice, sorted=sorted)
27 del eval_builtins['delattr']
28 del eval_builtins['setattr']
29 eval_globals = {'__builtins__':eval_builtins, '_getattr_':getattr, '_getitem_': lambda x,y: x[y]}
31 def underscorereplace_errors(e):
32 return (u'_' * (e.end - e.start), e.end)
34 codecs.register_error('underscorereplace', underscorereplace_errors)
36 def evaluate(item,cdict):
37 if isinstance(item,Expr):
38 return item.evaluate(cdict)
39 else:
40 return item
42 class InlineFuncsVisitor:
43 def __init__(self, filename, baseexpr):
44 self.filename = filename
45 self.baseexpr = baseexpr
46 def visitCallFunc(self, node, *args):
47 if not hasattr(node, 'node'):
48 return node
49 if not isinstance(node.node, ast.Name):
50 return node
51 handler = getattr(self, '_' + node.node.name, None)
52 if handler:
53 return handler(node, *args)
54 else:
55 return node
56 def _first(self, node, *args):
57 clocals = ast.Const(locals)
58 clocals.lineno = node.lineno
59 clocals = ast.CallFunc(clocals,[],None,None)
60 clocals.lineno = node.lineno
61 exp = ast.Or([])
62 exp.lineno = node.lineno
63 for item in node.args:
64 if not isinstance(item, ast.Const) or isinstance(item.value, basestring):
65 if isinstance(item, ast.Const):
66 item = item.value
67 item = self.baseexpr(item, self.filename)
68 item = ast.Const(item.evaluate)
69 item.lineno = node.lineno
70 item = ast.CallFunc(item, [clocals])
71 item.lineno = node.lineno
72 exp.nodes.append(item)
73 return exp
75 class Expr(RExpression,object):
76 _globals = eval_globals
77 _cache = {}
79 def __new__(cls, source, filename="", baseexpr=None):
80 key = (cls, source, filename, baseexpr)
81 if isinstance(source, basestring):
82 if key not in cls._cache:
83 cls._cache[key] = object.__new__(cls)
84 return cls._cache[key]
85 elif isinstance(source, ast.Node):
86 return object.__new__(cls)
87 elif isinstance(source,cls):
88 return source
90 def __init__(self, source, filename="", baseexpr=None):
91 if hasattr(self,'_compiled'):
92 return
93 self._source = source
94 self._baseexpr = baseexpr or getattr(self.__class__,'_baseexpr', None) or self.__class__
95 self._filename = filename
96 if not isinstance(source, ast.Node):
97 RExpression.__init__(self, source, filename)
98 source = self._get_tree()
99 else:
100 if not (isinstance(source, ast.Expression)):
101 source = ast.Expression(source)
102 source.filename = filename
103 walk(source, InlineFuncsVisitor(self._filename, self._baseexpr))
104 gen = self.CodeGeneratorClass(source)
105 self._compiled = gen.getCode()
107 def __hash__(self):
108 return hash(self._compiled)
110 def _get_tree(self):
111 tree = RExpression._get_tree(self)
112 walk(tree, InlineFuncsVisitor(self.filename, self._baseexpr))
113 return tree
115 def evaluate(self, cdict):
116 try:
117 return eval(self._compiled, self._globals, cdict)
118 except NameError:
119 return None
121 class StringExpr(Expr):
122 def evaluate(self, cdict):
123 ret = super(self.__class__,self).evaluate(cdict)
124 if ret is not None:
125 ret = unicode(ret)
126 return ret
128 class SanitizedExpr(Expr):
129 def evaluate(self, cdict):
130 ret = super(self.__class__,self).evaluate(cdict)
131 if ret is not None:
132 ret = unicode(ret).translate(pathseptrans)
133 return ret
135 class Format(Expr):
136 _sanitize = False
138 def _get_tree(self):
139 clocals = ast.Const(locals)
140 clocals.lineno = 1
141 clocals = ast.CallFunc(clocals, [], None, None)
142 clocals.lineno = 1
143 items = self._parse()
144 te = ast.Tuple([])
145 te.lineno = 1
146 ta = ast.Tuple([])
147 ta.lineno = 1
148 for item in items:
149 if isinstance(item, Expr):
150 item = ast.Const(item.evaluate)
151 item.lineno = 1
152 item = ast.CallFunc(item, [clocals])
153 item.lineno = 1
154 ta.nodes.append(item)
155 te.nodes.append(item)
156 else:
157 item = ast.Const(item)
158 item.lineno = 1
159 ta.nodes.append(item)
160 result = ast.Const(''.join)
161 result.lineno = 1
162 result = ast.CallFunc(result,[ta],None, None)
163 if te.nodes:
164 none = ast.Name('None')
165 none.lineno = 1
166 test = ast.Compare(none, [('in',te)])
167 test.lineno = 1
168 result = ast.IfExp(test, none, result)
169 result.lineno = 1
170 result = ast.Expression(result)
171 result.lineno = 1
172 result.filename = self._filename
173 return result
175 def _parse(self):
176 state = []
177 result = []
178 cur = []
179 prevend = 0
180 for m in breakre.finditer(self._source):
181 # import pdb; pdb.set_trace()
182 mt = m.group(0)
183 mg = m.groupdict()
184 if m.start() > prevend:
185 cur.append(self._source[prevend:m.start()])
186 prevend = m.end()
187 if not state:
188 if mt == '$$':
189 cur.append('$')
190 elif mt.startswith('$'):
191 if not (mg['label'] or mg['paren']):
192 cur.append(mt)
193 continue
194 if any(cur):
195 result.append(''.join(cur))
196 cur = []
197 if not mg['paren']:
198 if mg['nosan'] or not self._sanitize:
199 result.append(StringExpr(mg['label'], self._filename, self._baseexpr))
200 else:
201 result.append(SanitizedExpr(mg['label'], self._filename, self._baseexpr))
202 else:
203 if mg['nosan'] or not self._sanitize:
204 cur.append(StringExpr)
205 else:
206 cur.append(SanitizedExpr)
207 if mg['label']:
208 cur.append(mg['label'])
209 cur.append('(')
210 state.append('(')
211 else:
212 cur.append(mt)
213 else:
214 cur.append(mt)
215 if state[-1] == '(':
216 if mt == ')':
217 state.pop()
218 elif mg['quote']:
219 state.append(mg['quote'])
220 elif mt.endswith('('):
221 state.append('(')
222 else:
223 if mg['quote'] == state[-1]:
224 state.pop()
225 if not state:
226 result.append(cur[0](''.join(cur[1:]), self._filename, self._baseexpr))
227 cur = []
228 cur.append(self._source[prevend:])
229 if state:
230 raise SyntaxError('unexpected EOF while parsing',(self._filename,1,len(self._source),self._source))
231 if any(cur):
232 result.append(''.join(cur))
233 return result
235 class SanitizedFormat(Format):
236 _sanitize = True
238 class FileFormat(SanitizedFormat):
239 _baseexpr = SanitizedFormat
240 def evaluate(self, cdict):
241 ret = super(self.__class__,self).evaluate(cdict)
242 if ret is not None:
243 ret = ret.translate(pathtrans)
244 return ret
246 #class Format(Expr):
248 def unique(testset, expr, evalexpr): pass
250 __all__ = ['Format','FileFormat','Expr','evaluate']