__docformat__ = 'restructuredtext' from pattern import Pattern class Category: """ A Category is a "set" of states or events. The category specifies a name, certain arguments that must be provided, and constraints for the values of those arguments. Any possible state/event which meets these restrictions is in the category. """ def __init__(self, name, **args): """ Categories are constructed with a `name` and a series of keyword arguments. `name` is a string. Each keyword argument that is specified is the name of an argument a state or event must posess to be considered to be a member of the category. The value of the keyword arguments are patterns that must match the value of the argument. """ self.args = args self.name = name def superset_of(self, other): """ Determine if this category is a superset of `other`. """ if self.name != other.name: return False for key, value in self.args.iteritems(): if not other.args.has_key(key): return False if not other.args[key].intersect(value) == other.args[key]: return False return True def subtract(self, other): """ returns a set of categories that, when combined, include all items that are in `self` but *not* in `other` """ retval = set() for key, value in other.args.iteritems(): args = {} for k, v in self.args.iteritems(): args[k] = v args[key].intersect(value.complement()) if args[key].nonempty(): retval.add(Category(self.name, **args)) def inverse_set(self): """ Returns a set of Categories that will match any item NOT matched by this one (provided it specifies the same or more arguments). """ retval = set() for key, value in self.args.iteritems(): value = value.complement() if not value.nonempty(): continue newargs = self.args.copy() newargs[key] = value retval.add(Category(self.name, **newargs)) return retval def intersect_args(self, **args): """ `intersect` this Category with a new Category that has the same name and the provided args. """ return self.intersect(Category(self.name, **args)) def intersect(self, other): """ Returns a new category consisting only of items that are in this category, and the category `other`. """ if self.name != other.name: return None args = {} for key in set(self.args.keys() + other.args.keys()): if not self.args.has_key(key): args[key] = other.args[key] elif not other.args.has_key(key): args[key] = self.args[key] else: val = self.args[key].intersect(other.args[key]) if not val.nonempty(): return None args[key] = val return Category(self.name, **args) def __str__(self): return "%s(%s)" % ( self.name, ", ".join(["%s: %s" % (key, val) \ for key, val in self.args.iteritems()]) ) def __repr__(self): return self.__str__() def __hash__(self): return hash((self.argstup(), self.name)) def __eq__(self, other): if not other.__class__ == Category: return False return self.name == other.name and self.argstup() == other.argstup() def subset_of(self, other): return self.intersect(other) == self def argstup(self): """ Returns arguments as a sorted tuple. """ retval = [] keys = self.args.keys() keys.sort() for key in keys: if self.args[key].full(): continue retval.append((key, self.args[key])) return tuple(retval) def fill(self, info): """ Set any arguments matched by this category that don't have explicitly required values to require the values in `info`. """ args = {} for key, value in self.args.iteritems(): if not info.has_key(key): args[key] = value else: args[key] = value.intersect(info[key]) return Category(self.name, **args)