1 # Copyright 2008-2009 LShift Ltd |
1 """ |
2 # Author(s): |
2 Glob-based, order-based rules matcher that can answer "maybe" |
3 # Paul Crowley <paul@lshift.net> |
3 where the inputs make clear that something is unknown. |
4 # |
4 """ |
5 # This software may be used and distributed according to the terms |
|
6 # of the GNU General Public License, incorporated herein by reference. |
|
7 |
5 |
8 import sys |
6 import sys |
9 import re |
7 import re |
10 import os |
8 import os |
11 import os.path |
9 import os.path |
12 |
10 |
13 allowedchars = "A-Za-z0-9_-" |
|
14 |
|
15 def globmatcher(pattern): |
11 def globmatcher(pattern): |
16 p = "[^/]*".join(re.escape(c) for c in pattern.split("*")) |
12 p = "[^/]*".join(re.escape(c) for c in pattern.split("*")) |
17 # ** means "match recursively" ie "ignore directories" |
13 # ** means "match recursively" ie "ignore directories" |
18 rex = re.compile(p.replace("[^/]*[^/]*", ".*") + "$") |
14 return re.compile(p.replace("[^/]*[^/]*", ".*") + "$") |
19 # None matches everything |
|
20 return lambda x: x is None or rex.match(x) is not None |
|
21 |
15 |
|
16 # Returns True for a definite match |
|
17 # False for a definite non-match |
|
18 # None where we can't be sure because a key is None |
22 def rule(pairs): |
19 def rule(pairs): |
23 matchers = [(k, globmatcher(v)) for k, v in pairs] |
20 matchers = [(k, globmatcher(v)) for k, v in pairs] |
24 def c(kw): |
21 def c(kw): |
25 for k, m in matchers: |
22 for k, m in matchers: |
26 if k not in kw or not m(kw[k]): |
23 if k not in kw: |
|
24 return False |
|
25 kkw = kw[k] |
|
26 if kkw is None: |
|
27 return None |
|
28 if m.match(kkw) is None: |
27 return False |
29 return False |
28 return True |
30 return True |
29 return c |
31 return c |
30 |
32 |
31 class Ruleset(object): |
33 class Ruleset(object): |
44 self.preset.update(kw) |
46 self.preset.update(kw) |
45 |
47 |
46 def get(self, k): |
48 def get(self, k): |
47 return self.preset.get(k, None) |
49 return self.preset.get(k, None) |
48 |
50 |
49 def matchrule(self, kw): |
51 def matchrules(self, kw): |
50 d = self.preset.copy() |
52 d = self.preset.copy() |
51 d.update(kw) |
53 d.update(kw) |
|
54 res = set() |
52 for a, c in self.rules: |
55 for a, c in self.rules: |
53 if c(d): |
56 m = c(d) |
54 return a |
57 if m is None: |
55 return None |
58 # "Maybe match" - add it and carry on |
|
59 res.add(a) |
|
60 elif m: |
|
61 # Definite match - add it and stop |
|
62 res.add(a) |
|
63 break |
|
64 return res |
56 |
65 |
57 def allow(self, level, **kw): |
66 def allow(self, level, **kw): |
58 a = self.matchrule(kw) |
67 for a in self.matchrules(kw): |
59 return a in self.levels and self.levels.index(a) <= self.levels.index(level) |
68 if a in self.levels: |
|
69 if self.levels.index(a) <= self.levels.index(level): |
|
70 return True |
|
71 return False |
60 |
72 |
61 def readfile(self, fn): |
73 def readfile(self, fn): |
|
74 f = open(fn) |
62 try: |
75 try: |
63 f = open(fn) |
76 for l in f: |
64 try: |
77 l = l.strip() |
65 for l in f: |
78 if len(l) == 0 or l.startswith("#"): |
66 l = l.strip() |
79 continue |
67 if len(l) == 0 or l.startswith("#"): |
80 l = l.split() |
68 continue |
81 self.add(l[0], rule([c.split("=", 1) for c in l[1:]])) |
69 l = l.split() |
82 finally: |
70 self.add(l[0], rule([c.split("=", 1) for c in l[1:]])) |
83 f.close() |
71 finally: |
|
72 f.close() |
|
73 except Exception, e: |
|
74 print >> sys.stderr, "Failure reading rules file:", e |
|
75 |
84 |
76 rules = Ruleset() |
85 rules = Ruleset() |
77 |
86 |