summaryrefslogtreecommitdiffstats
path: root/category.py
blob: 61458dc6e1a5c0e18c8480373037418871703e76 (plain)
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
__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 equiv(self, other):
        """
        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 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
        category, and the category `other`.
        """
        if self.name != other.name: return None
        args = {}
        for key in list(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):
        return self.name == other.name and self.argstup() == other.argstup()

    def argstup(self):
        """
        Returns arguments as a sorted tuple.
        """
        retval = []
        keys = self.args.keys()
        keys.sort()
        for key in keys:
            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)