1# This file is part of Buildbot.  Buildbot is free software: you can
2# redistribute it and/or modify it under the terms of the GNU General Public
3# License as published by the Free Software Foundation, version 2.
4#
5# This program is distributed in the hope that it will be useful, but WITHOUT
6# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
7# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
8# details.
9#
10# You should have received a copy of the GNU General Public License along with
11# this program; if not, write to the Free Software Foundation, Inc., 51
12# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
13#
14# Copyright Buildbot Team Members
15
16import re
17
18_ident_re = re.compile('^[a-zA-Z_-][.a-zA-Z0-9_-]*$')
19
20
21def ident(x):
22    if _ident_re.match(x):
23        return x
24    raise TypeError
25
26
27class Matcher:
28
29    def __init__(self):
30        self._patterns = {}
31        self._dirty = True
32
33    def __setitem__(self, path, value):
34        assert path not in self._patterns, "duplicate path {}".format(path)
35        self._patterns[path] = value
36        self._dirty = True
37
38    def __repr__(self):
39        return '<Matcher %r>' % (self._patterns,)
40
41    path_elt_re = re.compile('^(.?):([a-z0-9_.]+)$')
42    type_fns = dict(n=int, i=ident)
43
44    def __getitem__(self, path):
45        if self._dirty:
46            self._compile()
47
48        patterns = self._by_length.get(len(path), {})
49        for pattern in patterns:
50            kwargs = {}
51            for pattern_elt, path_elt in zip(pattern, path):
52                mo = self.path_elt_re.match(pattern_elt)
53                if mo:
54                    type_flag, arg_name = mo.groups()
55                    if type_flag:
56                        try:
57                            type_fn = self.type_fns[type_flag]
58                        except Exception:
59                            assert type_flag in self.type_fns, \
60                                    "no such type flag {}".format(type_flag)
61                        try:
62                            path_elt = type_fn(path_elt)
63                        except Exception:
64                            break
65                    kwargs[arg_name] = path_elt
66                else:
67                    if pattern_elt != path_elt:
68                        break
69            else:
70                # complete match
71                return patterns[pattern], kwargs
72        else:
73            raise KeyError('No match for %r' % (path,))
74
75    def iterPatterns(self):
76        return list(self._patterns.items())
77
78    def _compile(self):
79        self._by_length = {}
80        for k, v in self.iterPatterns():
81            length = len(k)
82            self._by_length.setdefault(length, {})[k] = v
83