1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
__docformat__ = 'restructuredtext'
from pattern import Pattern
class Category:
"""
A Category is a "set" of states. The category specifies a name, certain
arguments that must be provided, and constraints for the values of those
arguments. Any possible state 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 a state argument a state 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 state 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 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 inverse_set(self):
"""
Returns a set of Categories that will match any state 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(self, other):
"""
Returns a new category consisting only of states 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
elif not value.singular():
args[key] = info[key]
else:
args[key] = value
return Category(self.name, **args)
|