1
# Copyright (C) 2006 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
"""Convert a glob pattern into a regular expression.
23
We handle the following patterns:
24
** Match a string of characters (including dir separators)
25
* Match a string of characters (not directory separator)
26
? Match a single character (not directory separator)
27
[seq] Matches a single character, but any of 'seq'
28
[!seq] Match any single character not in 'seq'
30
This was adapted from fnmatch.translate()
32
:param pat: The pattern to transform
33
:return: A regular expression
42
if pat[i:i+1] == '*': # pattern '**'
46
res = res + r'[^/\\]*'
51
if j < n and pat[j] == '!':
53
if j < n and pat[j] == ']':
55
while j < n and pat[j] != ']':
60
stuff = pat[i:j].replace('\\','\\\\')
63
stuff = '^' + stuff[1:] + r'/\\'
66
res = '%s[%s]' % (res, stuff)
68
res = res + re.escape(c)
69
# Without a final $, re.match() will match if just the beginning
70
# matches. I did not expect that. I thought re.match() had to match
75
class _GlobMatcher(object):
76
"""A class which handles matching filenames to glob expressions"""
78
def __init__(self, glob_re):
79
"""Create a matcher from a regular expression."""
80
self._compiled_re = re.compile(glob_re, re.UNICODE)
82
def __call__(self, fname):
83
"""See if fname matches the internal glob.
85
:param fname: A filename to check.
86
:return: Boolean, does/doesn't match
88
return self._compiled_re.match(fname) is not None
91
def glob_to_matcher(glob):
92
"""Return a callable which will match filenames versus the glob."""
93
return _GlobMatcher(glob_to_re(glob))
96
def globs_to_re(patterns):
97
"""Convert a set of patterns into a single regular expression.
99
:param patterns: A list of patterns to transform
100
:return: A regular expression combining all patterns
104
pat_re = glob_to_re(pat)
105
assert pat_re[-1] == '$'
106
# TODO: jam 20060107 It seems to be enough to do:
107
# (pat1|pat2|pat3|pat4)$
108
# Is there a circumstance where we need to do
109
# ((pat1)|(pat2)|(pat3))$
111
# TODO: jam 20060107 Is it more efficient to do:
114
# (pat1$)|(pat2$)|(pat3$)
115
# I thought it would be more efficent to only have to
116
# match the end of the pattern once
118
#final_re.append('(' + pat_re[:-1] + ')')
119
final_re.append(pat_re[:-1])
120
# All patterns end in $, we don't need to specify it
122
# Just put one at the end
123
return '(' + '|'.join(final_re) + ')$'
126
def globs_to_matcher(patterns):
127
"""Return a callable which will match filenames versus the globs."""
128
return _GlobMatcher(globs_to_re(patterns))