src/mercurialserver/ruleset.py
author Paul Crowley <paul@lshift.net>
Mon, 04 Jan 2010 17:15:06 +0000
changeset 252 75acaf1b1216
parent 242 03d8f07230b3
child 297 9875791ab421
permissions -rw-r--r--
Add section on logging, merge later sections

"""
Glob-based, order-based rules matcher that can answer "maybe"
where the inputs make clear that something is unknown.
"""

import sys
import re
import os
import os.path

def globmatcher(pattern):
    p = "[^/]*".join(re.escape(c) for c in pattern.split("*"))
    # ** means "match recursively" ie "ignore directories"
    return re.compile(p.replace("[^/]*[^/]*", ".*") + "$")

# Returns True for a definite match
# False for a definite non-match
# None where we can't be sure because a key is None
def rule(pairs):
    matchers = [(k, globmatcher(v)) for k, v in pairs]
    def c(kw):
        for k, m in matchers:
            if k not in kw:
                return False
            kkw = kw[k]
            if kkw is None:
                return None
            if m.match(kkw) is None:
                return False
        return True
    return c

class Ruleset(object):
    '''Class representing the rules in a rule file'''
    
    levels = ["init", "write", "read", "deny"]

    def __init__(self):
        self.rules = []
        self.preset = {}

    def add(self, action, conditions):
        self.rules.append((action, conditions))

    def set(self, **kw):
        self.preset.update(kw)
        
    def get(self, k):
        return self.preset.get(k, None)
        
    def matchrules(self, kw):
        d = self.preset.copy()
        d.update(kw)
        res = set()
        for a, c in self.rules:
            m = c(d)
            if m is None:
                # "Maybe match" - add it and carry on
                res.add(a)
            elif m:
                # Definite match - add it and stop
                res.add(a)
                break
        return res

    def allow(self, level, **kw):
        for a in self.matchrules(kw):
            if a in self.levels:
                if self.levels.index(a) <= self.levels.index(level):
                    return True
        return False
    
    def readfile(self, fn):
        f = open(fn)
        try:
            for l in f:
                l = l.strip()
                if len(l) == 0 or l.startswith("#"):
                    continue
                l = l.split()
                self.add(l[0], rule([c.split("=", 1) for c in l[1:]]))
        finally:
            f.close()

rules = Ruleset()