1 """
2
3 a rule system for ewa, used to determine what files should appear
4 before or after a main mp3 file in a composite mp3.
5
6 Rules are callables that take a single "filename" parameter and return
7 None or a generator that yields mp3 filenames (or equivalent
8 designations) in sequence.
9
10 A RuleList is a rule with a list of subrules, optionally with a
11 condition (matched against the filename). When the RuleList is
12 called, if the condition does not exist, or if it matches, each
13 subrule is called on the filename until one returns something, which
14 is the return value.
15
16 Rules can be marshalled to and from JSON, and from (but currently not
17 to) the ewa rule configuration format implemented in ewa.ruleparser.
18
19 """
20
21
22 import datetime
23 import fnmatch
24 import os
25 import re
26 from string import Template
27 import time
28
29 try:
30 import simplejson as json
31 except ImportError:
32 import json
33
34 from ewa.logutil import warn
38
41
43 if isinstance(obj, datetime.date):
44 return dict(year=obj.year, month=obj.month, day=obj.day)
45 elif isinstance(obj, datetime.datetime):
46
47 return dict(year=obj.year,
48 month=obj.month,
49 day=obj.day,
50 hour=obj.hour,
51 minute=obj.minute,
52 second=obj.second,
53 microsecond=obj.microsecond)
54
55 elif isinstance(obj, list):
56 return [to_jsondata(x) for x in obj]
57 elif isinstance(obj, tuple):
58 return tuple(to_jsondata(x) for x in obj)
59 try:
60 return obj.to_jsondata()
61 except AttributeError:
62 return obj
63
67
69
73
75 if self.cond:
76 m=self.cond.match(filename)
77 if not m:
78 return
79 for r in self.rules:
80 res=r(filename)
81 if res:
82 return res
83
86 """
87 this may be useful as the last rule in a rule-list;
88 it yields the filename passed and nothing else
89 """
92
94 - def __init__(self, matcher, pre=None, post=None):
95 """
96 the matcher is a callable with a "match" method. pre and post
97 and lists of things that go before and after the filename
98 passed in.
99 """
100 self.matcher=matcher
101 self.pre=pre or []
102 self.post=post or []
103
105 if hasattr(match, 'groupdict'):
106
107 d=dict((str(i+1), v) for i, v in enumerate(match.groups()))
108 d.update(match.groupdict())
109 expand=lambda s: match.expand(_template(f).safe_substitute(d))
110 else:
111 expand=lambda s: s
112
113 for f in self.pre:
114 yield expand(f)
115
116 yield OriginalName(filename)
117
118 for f in self.post:
119 yield expand(f)
120
121
123 if self.matcher is None:
124 return True
125 return self.matcher.match(filename)
126
128 m=self._match(filename)
129 if m:
130 return self._gen_list(filename, m)
131
132 -class And(_jsonable):
134 self.submatchers=submatchers
135
136 - def match(self, target):
137 res=False
138 for m in self.submatchers:
139 res=m.match(target)
140 if not res:
141 return False
142 return res
143
144 -class Or(_jsonable):
146 self.submatchers=submatchers
147
148 - def match(self, target):
149 for m in self.submatchers:
150 res=m.match(target)
151 if res:
152 return res
153 return False
154
155 -class Not(_jsonable):
158
159 - def match(self, target):
160 return not self.matcher.match(target)
161
164 self.regex=regex
165 self.flags=flags
166
167 - def match(self, target):
168 return re.match(self.regex, target, self.flags)
169
171 - def __init__(self, pattern, casesensitive=True):
174
175 - def match(self, target):
176 if self.casesensitive:
177 return fnmatch.fnmatchcase(target, self.pattern)
178 else:
179 return fnmatch.fnmatch(target, self.pattern)
180
182 m=re.search(regex, target)
183 if m:
184 try:
185 ttuple=time.strptime(m.group(), format)
186 except ValueError:
187 warn("error in time format: %s from %s", m.group(), target)
188 else:
189 return datetime.datetime(*ttuple[:6])
190 return None
191
193 """
194 returns true if the current time falls within a
195 datetime range
196 """
197
198 - def __init__(self,
199 start=datetime.datetime.min,
200 end=datetime.datetime.max):
201 self.start=start
202 self.end=end
203
204 - def match(self, target):
205 return self.start <= datetime.datetime.now() <= self.end
206
210 """
211 returns true if a date encoded in a string falls within
212 a date range
213 """
214 - def __init__(self,
215 start=datetime.datetime.min,
216 end=datetime.datetime.max,
217 dateregex=r'\d{6}',
218 dateformat='%m%d%y'):
219 self.start=start
220 self.end=end
221 self.dateregex=dateregex
222 self.dateformat=dateformat
223
224 - def match(self, target):
225 date=extract_datetime(target, self.dateregex, self.dateformat)
226 if not date:
227 return False
228 return self.start <= date <= self.end
229
230 -def RegexRule(pattern, pre=None, post=None, flags=0):
233
234 -def GlobMatchRule(pattern, pre=None, post=None, casesensitive=True):
237
238 _json_registry=dict((x.__name__, x) for x in (datetime.date,
239 datetime.datetime,
240 RuleList,
241 DefaultRule,
242 MatchRule,
243 And,
244 Or,
245 Not,
246 RegexMatcher,
247 GlobMatcher,
248 CurrentTimeMatch,
249 FileTimeMatch))
262
267
271
281
283
284 - def __init__(self, rulefile, refresh=15, format=None):
285 self.rulefile=rulefile
286 self._refresh=refresh
287 if format is None:
288 if rulefile.endswith('.json') or rulefile.endswith('.js'):
289 format='json'
290 elif rulefile.endswith('.py'):
291 format='python'
292 else:
293 format='ewaconf'
294 self.format=format
295 self._load_rule()
296
297 @staticmethod
299 filename=os.path.abspath(pyfile)
300 s=open(filename).read()
301 codeobj=compile(s, filename, 'exec')
302 env={}
303 exec codeobj in {}, env
304
305 return env['rules']
306
307 - def _load_rule(self, mtime=None, lastchecked=None):
308 if mtime is None:
309 mtime=os.path.getmtime(self.rulefile)
310 if lastchecked is None:
311 lastchecked=time.time()
312 self._modified=mtime
313 if self.format=='json':
314 self._rule=from_json(open(self.rulefile).read())
315 elif self.format=='python':
316 self._rule=self._rules_from_python(self.rulefile)
317 elif self.format=='ewaconf':
318 self._rule=_parse_ewaconf(self.rulefile)
319 else:
320 raise ValueError, "unrecognized format: %s" % self.format
321 self._lastchecked=lastchecked
322
324 t=time.time()
325 if (t-self._lastchecked) > self._refresh:
326 m=os.path.getmtime(self.rulefile)
327 if m > self._modified:
328 self._load_rule(m,t)
329
331 self._check()
332 return self._rule(filename)
333
334 __all__=[
335 'RuleList',
336 'DefaultRule',
337 'MatchRule',
338 'And',
339 'Or',
340 'Not',
341 'RegexMatcher',
342 'GlobMatcher',
343 'extract_datetime',
344 'CurrentTimeMatch',
345 'FileTimeMatch',
346 'RegexRule',
347 'GlobMatchRule',
348 'from_json',
349 'to_json',
350 'FileRule'
351 ]
352