1
# Copyright (C) 2006, 2008-2011, 2017 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Lazily compiled regex objects.
19
This module defines a class which creates proxy objects for regex
20
compilation. This allows overriding re.compile() to return lazily compiled
23
We do this rather than just providing a new interface so that it will also
24
be used by existing Python modules that create regexs.
32
class InvalidPattern(errors.BzrError):
34
_fmt = ('Invalid pattern(s) found. %(msg)s')
36
def __init__(self, msg):
40
class LazyRegex(object):
41
"""A proxy around a real regex, which won't be compiled until accessed."""
43
# These are the parameters on a real _sre.SRE_Pattern object, which we
44
# will map to local members so that we don't have the proxy overhead.
45
_regex_attributes_to_copy = [
46
'__copy__', '__deepcopy__', 'findall', 'finditer', 'match',
47
'scanner', 'search', 'split', 'sub', 'subn'
50
# We use slots to keep the overhead low. But we need a slot entry for
51
# all of the attributes we will copy
52
__slots__ = ['_real_regex', '_regex_args', '_regex_kwargs',
53
] + _regex_attributes_to_copy
55
def __init__(self, args, kwargs):
56
"""Create a new proxy object, passing in the args to pass to re.compile
58
:param args: The `*args` to pass to re.compile
59
:param kwargs: The `**kwargs` to pass to re.compile
61
self._real_regex = None
62
self._regex_args = args
63
self._regex_kwargs = kwargs
65
def _compile_and_collapse(self):
66
"""Actually compile the requested regex"""
67
self._real_regex = self._real_re_compile(*self._regex_args,
69
for attr in self._regex_attributes_to_copy:
70
setattr(self, attr, getattr(self._real_regex, attr))
72
def _real_re_compile(self, *args, **kwargs):
73
"""Thunk over to the original re.compile"""
75
return re.compile(*args, **kwargs)
77
# raise InvalidPattern instead of re.error as this gives a
78
# cleaner message to the user.
79
raise InvalidPattern('"' + args[0] + '" ' + str(e))
81
def __getstate__(self):
82
"""Return the state to use when pickling."""
84
"args": self._regex_args,
85
"kwargs": self._regex_kwargs,
88
def __setstate__(self, dict):
89
"""Restore from a pickled state."""
90
self._real_regex = None
91
setattr(self, "_regex_args", dict["args"])
92
setattr(self, "_regex_kwargs", dict["kwargs"])
94
def __getattr__(self, attr):
95
"""Return a member from the proxied regex object.
97
If the regex hasn't been compiled yet, compile it
99
if self._real_regex is None:
100
self._compile_and_collapse()
101
# Once we have compiled, the only time we should come here
102
# is actually if the attribute is missing.
103
return getattr(self._real_regex, attr)
106
def lazy_compile(*args, **kwargs):
107
"""Create a proxy object which will compile the regex on demand.
109
:return: a LazyRegex proxy object.
111
return LazyRegex(args, kwargs)