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
39
43
46 if isinstance(obj, datetime.date):
47 return dict(year=obj.year, month=obj.month, day=obj.day)
48 elif isinstance(obj, datetime.datetime):
49
50 return dict(year=obj.year,
51 month=obj.month,
52 day=obj.day,
53 hour=obj.hour,
54 minute=obj.minute,
55 second=obj.second,
56 microsecond=obj.microsecond)
57
58 elif isinstance(obj, list):
59 return [to_jsondata(x) for x in obj]
60 elif isinstance(obj, tuple):
61 return tuple(to_jsondata(x) for x in obj)
62 try:
63 return obj.to_jsondata()
64 except AttributeError:
65 return obj
66
73
76
80
82 if self.cond:
83 m = self.cond.match(filename)
84 if not m:
85 return
86 for r in self.rules:
87 res = r(filename)
88 if res:
89 return res
90
93 """
94 this may be useful as the last rule in a rule-list;
95 it yields the filename passed and nothing else
96 """
99
102 - def __init__(self, matcher, pre=None, post=None):
103 """
104 the matcher is a callable with a "match" method. pre and post
105 and lists of things that go before and after the filename
106 passed in.
107 """
108 self.matcher = matcher
109 self.pre = pre or []
110 self.post = post or []
111
113 if hasattr(match, 'groupdict'):
114
115 d = dict((str(i + 1), v) for i, v in enumerate(match.groups()))
116 d.update(match.groupdict())
117 expand = lambda s: match.expand(_template(f).safe_substitute(d))
118 else:
119 expand = lambda s: s
120
121 for f in self.pre:
122 yield expand(f)
123
124 yield OriginalName(filename)
125
126 for f in self.post:
127 yield expand(f)
128
130 if self.matcher is None:
131 return True
132 return self.matcher.match(filename)
133
135 m = self._match(filename)
136 if m:
137 return self._gen_list(filename, m)
138
139
140 -class And(_jsonable):
142 self.submatchers = submatchers
143
144 - def match(self, target):
145 res = False
146 for m in self.submatchers:
147 res = m.match(target)
148 if not res:
149 return False
150 return res
151
152
153 -class Or(_jsonable):
155 self.submatchers = submatchers
156
157 - def match(self, target):
158 for m in self.submatchers:
159 res = m.match(target)
160 if res:
161 return res
162 return False
163
164
165 -class Not(_jsonable):
167 self.matcher = matcher
168
169 - def match(self, target):
170 return not self.matcher.match(target)
171
175 self.regex = regex
176 self.flags = flags
177
178 - def match(self, target):
179 return re.match(self.regex, target, self.flags)
180
183 - def __init__(self, pattern, casesensitive=True):
186
187 - def match(self, target):
188 if self.casesensitive:
189 return fnmatch.fnmatchcase(target, self.pattern)
190 else:
191 return fnmatch.fnmatch(target, self.pattern)
192
195 m = re.search(regex, target)
196 if m:
197 try:
198 ttuple = time.strptime(m.group(), format)
199 except ValueError:
200 warn("error in time format: %s from %s", m.group(), target)
201 else:
202 return datetime.datetime(*ttuple[:6])
203 return None
204
207 """
208 returns true if the current time falls within a
209 datetime range
210 """
211
212 - def __init__(self,
213 start=datetime.datetime.min,
214 end=datetime.datetime.max):
215 self.start = start
216 self.end = end
217
218 - def match(self, target):
219 return self.start <= datetime.datetime.now() <= self.end
220
223 """
224 returns true if a date encoded in a string falls within
225 a date range
226 """
227 - def __init__(self,
228 start=datetime.datetime.min,
229 end=datetime.datetime.max,
230 dateregex=r'\d{6}',
231 dateformat='%m%d%y'):
232 self.start = start
233 self.end = end
234 self.dateregex = dateregex
235 self.dateformat = dateformat
236
237 - def match(self, target):
238 date = extract_datetime(target, self.dateregex, self.dateformat)
239 if not date:
240 return False
241 return self.start <= date <= self.end
242
243
244 -def RegexRule(pattern, pre=None, post=None, flags=0):
247
248
249 -def GlobMatchRule(pattern, pre=None, post=None, casesensitive=True):
252
253
254 _json_registry = dict((x.__name__, x) \
255 for x in (datetime.date,
256 datetime.datetime,
257 RuleList,
258 DefaultRule,
259 MatchRule,
260 And,
261 Or,
262 Not,
263 RegexMatcher,
264 GlobMatcher,
265 CurrentTimeMatch,
266 FileTimeMatch))
270 if isinstance(data, dict) and len(data) == 1:
271 key = data.keys()[0]
272 if key in _json_registry:
273 obj = _json_registry[key]
274 kwargs = dict((str(k), from_jsondata(v)) \
275 for k, v in data[key].iteritems())
276 return obj(**kwargs)
277
278 if isinstance(data, list):
279 return [from_jsondata(x) for x in data]
280 return data
281
286
291
302
305
306 - def __init__(self, rulefile, refresh=15, format=None):
307 self.rulefile = rulefile
308 self._refresh = refresh
309 if format is None:
310 if rulefile.endswith('.json') or rulefile.endswith('.js'):
311 format = 'json'
312 elif rulefile.endswith('.py'):
313 format = 'python'
314 else:
315 format = 'ewaconf'
316 self.format = format
317 self._load_rule()
318
319 @staticmethod
321 filename = os.path.abspath(pyfile)
322 s = open(filename).read()
323 codeobj = compile(s, filename, 'exec')
324 env = {}
325 exec codeobj in {}, env
326
327 return env['rules']
328
329 - def _load_rule(self, mtime=None, lastchecked=None):
330 if mtime is None:
331 mtime = os.path.getmtime(self.rulefile)
332 if lastchecked is None:
333 lastchecked = time.time()
334 self._modified = mtime
335 if self.format == 'json':
336 self._rule = from_json(open(self.rulefile).read())
337 elif self.format == 'python':
338 self._rule = self._rules_from_python(self.rulefile)
339 elif self.format == 'ewaconf':
340 self._rule = _parse_ewaconf(self.rulefile)
341 else:
342 raise ValueError("unrecognized format: %s" % self.format)
343 self._lastchecked = lastchecked
344
346 t = time.time()
347 if (t - self._lastchecked) > self._refresh:
348 m = os.path.getmtime(self.rulefile)
349 if m > self._modified:
350 self._load_rule(m, t)
351
353 self._check()
354 return self._rule(filename)
355
356 __all__ = [
357 'RuleList',
358 'DefaultRule',
359 'MatchRule',
360 'And',
361 'Or',
362 'Not',
363 'RegexMatcher',
364 'GlobMatcher',
365 'extract_datetime',
366 'CurrentTimeMatch',
367 'FileTimeMatch',
368 'RegexRule',
369 'GlobMatchRule',
370 'from_json',
371 'to_json',
372 'FileRule',
373 ]
374