Passed
Push — master ( d0bccf...cb153d )
by Max
58s
created

structured_data.match   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 110
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 77
dl 0
loc 110
rs 10
c 0
b 0
f 0
wmc 30

5 Functions

Rating   Name   Duplication   Size   Complexity  
A names() 0 3 1
A _match() 0 6 2
A _multi_index() 0 6 5
A _as_name() 0 4 2
A _match_iteration() 0 12 5

11 Methods

Rating   Name   Duplication   Size   Complexity  
A MatchDict.__iter__() 0 2 1
A MatchDict.__setitem__() 0 5 2
A Matchable.match() 0 7 2
A Matchable.__getitem__() 0 4 2
A Matchable.__bool__() 0 2 1
A MatchDict.__len__() 0 2 1
A MatchDict.__getitem__() 0 5 2
A Matchable.__call__() 0 2 1
A MatchDict.__init__() 0 2 1
A Matchable.__init__() 0 3 1
A MatchDict.__delitem__() 0 2 1
1
import collections
2
3
from ._attribute_constructor import AttributeConstructor
4
from ._match_failure import MatchFailure
5
from ._not_in import not_in
6
from ._patterns import DISCARD
7
from ._patterns import Pattern
8
from ._processors import PROCESSORS
9
10
11
def names(target):
12
    """Return every name bound by a target."""
13
    return PROCESSORS.names(target)
14
15
16
def _as_name(key):
17
    if isinstance(key, Pattern):
18
        return key.name
19
    return key
20
21
22
def _multi_index(dct, key):
23
    if isinstance(key, tuple):
24
        return tuple(dct[sub_key] for sub_key in key)
25
    if isinstance(key, dict):
26
        return {name: dct[value] for (name, value) in key.items()}
27
    raise KeyError(key)
28
29
30
class MatchDict(collections.abc.MutableMapping):
31
32
    def __init__(self):
33
        self.data = {}
34
35
    def __getitem__(self, key):
36
        key = _as_name(key)
37
        if isinstance(key, str):
38
            return self.data[key]
39
        return _multi_index(self, key)
40
41
    def __setitem__(self, key, value):
42
        key = _as_name(key)
43
        if not isinstance(key, str):
44
            raise TypeError
45
        self.data[key] = value
46
47
    def __delitem__(self, key):
48
        del self.data[_as_name(key)]
49
50
    def __iter__(self):
51
        yield from self.data
52
53
    def __len__(self):
54
        return len(self.data)
55
56
57
def _match_iteration(match_dict, target, value):
58
    if target is DISCARD:
59
        return
60
    if isinstance(target, Pattern):
61
        not_in(match_dict, target.name)
62
        match_dict[target.name] = value
63
        return
64
    processor = PROCESSORS.get_processor(target)
65
    if processor:
66
        yield from zip(processor(target), processor(value))
67
    elif target != value:
68
        raise MatchFailure
69
70
71
def _match(target, value):
72
    match_dict = MatchDict()
73
    to_process = [(target, value)]
74
    while to_process:
75
        to_process.extend(_match_iteration(match_dict, *to_process.pop()))
76
    return match_dict
77
78
79
class Matchable:
80
    """Given a value, attempt to match against a target."""
81
82
    def __init__(self, value):
83
        self.value = value
84
        self.matches = None
85
86
    def match(self, target):
87
        """Match against target, generating a set of bindings."""
88
        try:
89
            self.matches = _match(target, self.value)
90
        except MatchFailure:
91
            self.matches = None
92
        return self
93
94
    def __call__(self, target):
95
        return self.match(target)
96
97
    def __getitem__(self, key):
98
        if self.matches is None:
99
            raise ValueError
100
        return self.matches[key]
101
102
    def __bool__(self):
103
        return self.matches is not None
104
105
106
pat = AttributeConstructor(Pattern)
107
108
109
__all__ = ['Pattern', 'Matchable', 'names', 'pat']
110