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.
27
from __future__ import absolute_import
34
class InvalidPattern(errors.BzrError):
36
_fmt = ('Invalid pattern(s) found. %(msg)s')
38
def __init__(self, msg):
42
class LazyRegex(object):
43
"""A proxy around a real regex, which won't be compiled until accessed."""
45
# These are the parameters on a real _sre.SRE_Pattern object, which we
46
# will map to local members so that we don't have the proxy overhead.
47
_regex_attributes_to_copy = [
48
'__copy__', '__deepcopy__', 'findall', 'finditer', 'match',
49
'scanner', 'search', 'split', 'sub', 'subn'
52
# We use slots to keep the overhead low. But we need a slot entry for
53
# all of the attributes we will copy
54
__slots__ = ['_real_regex', '_regex_args', '_regex_kwargs',
55
] + _regex_attributes_to_copy
57
def __init__(self, args, kwargs):
58
"""Create a new proxy object, passing in the args to pass to re.compile
60
:param args: The `*args` to pass to re.compile
61
:param kwargs: The `**kwargs` to pass to re.compile
63
self._real_regex = None
64
self._regex_args = args
65
self._regex_kwargs = kwargs
67
def _compile_and_collapse(self):
68
"""Actually compile the requested regex"""
69
self._real_regex = self._real_re_compile(*self._regex_args,
71
for attr in self._regex_attributes_to_copy:
72
setattr(self, attr, getattr(self._real_regex, attr))
74
def _real_re_compile(self, *args, **kwargs):
75
"""Thunk over to the original re.compile"""
77
return re.compile(*args, **kwargs)
79
# raise InvalidPattern instead of re.error as this gives a
80
# cleaner message to the user.
81
raise InvalidPattern('"' + args[0] + '" ' + str(e))
83
def __getstate__(self):
84
"""Return the state to use when pickling."""
86
"args": self._regex_args,
87
"kwargs": self._regex_kwargs,
90
def __setstate__(self, dict):
91
"""Restore from a pickled state."""
92
self._real_regex = None
93
setattr(self, "_regex_args", dict["args"])
94
setattr(self, "_regex_kwargs", dict["kwargs"])
96
def __getattr__(self, attr):
97
"""Return a member from the proxied regex object.
99
If the regex hasn't been compiled yet, compile it
101
if self._real_regex is None:
102
self._compile_and_collapse()
103
# Once we have compiled, the only time we should come here
104
# is actually if the attribute is missing.
105
return getattr(self._real_regex, attr)
108
def lazy_compile(*args, **kwargs):
109
"""Create a proxy object which will compile the regex on demand.
111
:return: a LazyRegex proxy object.
113
return LazyRegex(args, kwargs)