summaryrefslogtreecommitdiffstats
path: root/category.py
diff options
context:
space:
mode:
Diffstat (limited to 'category.py')
-rw-r--r--category.py67
1 files changed, 37 insertions, 30 deletions
diff --git a/category.py b/category.py
index 51a36b8..61458dc 100644
--- a/category.py
+++ b/category.py
@@ -1,5 +1,7 @@
__docformat__ = 'restructuredtext'
+from pattern import Pattern
+
class Category:
"""
A Category is a "set" of states. The category specifies a name, certain
@@ -13,26 +15,38 @@ class Category:
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 a state argument a state must posess to be considered to
- be a member of the category. If the value of the keyword argument is
- `None` then the state argument can have any value, otherwise the value is a string
- and specifies a mandatory value for the state argument.
+ be a member of the category. The value of the keyword arguments are
+ patterns that must match the value of the state argument.
"""
self.args = args
self.name = name
def equiv(self, other):
"""
- Returns True if any state in the Category `other` is in this Category. Note that
- this is *intransitive*; ``a.equiv(b)`` is not the same as ``b.equiv(a)``.
+ A Category is equivalent to another Category if:
+ - The other category mandates the same or more arguments
+ - The intersection of the two is nonempty
+ Its an odd relationship, brought about to match expected user behavior
"""
if self.name != other.name: return False
for key, value in self.args.iteritems():
if not other.args.has_key(key): return False
- if value == None: continue
- if other.args[key] == None: continue
- if not other.args[key] == value: return False
+ if not other.args[key].intersect(value).nonempty(): return False
return True
-
+
+ def subtract(self, other):
+ """
+ returns a set of categories that, when combined, include all states
+ 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 intersect(self, other):
"""
Returns a new category consisting only of states that are in this
@@ -45,28 +59,19 @@ class Category:
args[key] = other.args[key]
elif not other.args.has_key(key):
args[key] = self.args[key]
- elif self.args[key] == None:
- args[key] = other.args[key]
- elif other.args[key] == None:
- args[key] = self.args[key]
- elif self.args[key] != other.args[key]:
- return None
- else: # self.args[key] == other.args[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):
- string = self.name + "("
- had = False
- for key, val in self.args.iteritems():
- if had:
- string += ", "
- had = True
- if val == None:
- string += "%%%s" % key
- else:
- string += "%s: %s" % (key, val)
- return string + ")"
+ return "%s(%s)" % (
+ self.name,
+ ", ".join(["%s: %s" % (key, val) \
+ for key, val in self.args.iteritems()])
+ )
def __repr__(self):
return self.__str__()
@@ -95,8 +100,10 @@ class Category:
"""
args = {}
for key, value in self.args.iteritems():
- if value != None or not info.has_key(key):
+ if not info.has_key(key):
args[key] = value
- else:
+ elif not value.singular():
args[key] = info[key]
+ else:
+ args[key] = value
return Category(self.name, **args)