~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/glob_matcher.py

  • Committer: John Arbash Meinel
  • Date: 2006-03-08 14:31:23 UTC
  • mfrom: (1598 +trunk)
  • mto: (1685.1.1 bzr-encoding)
  • mto: This revision was merged to the branch mainline in revision 1752.
  • Revision ID: john@arbash-meinel.com-20060308143123-448308b0db4de410
[merge] bzr.dev 1573, lots of updates

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006 Canonical Ltd
 
2
 
 
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.
 
7
 
 
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.
 
12
 
 
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
 
16
 
 
17
import re
 
18
 
 
19
 
 
20
def glob_to_re(pat):
 
21
    """Convert a glob pattern into a regular expression.
 
22
 
 
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'
 
29
 
 
30
    This was adapted from fnmatch.translate()
 
31
 
 
32
    :param pat: The pattern to transform
 
33
    :return: A regular expression
 
34
    """
 
35
 
 
36
    i, n = 0, len(pat)
 
37
    res = ''
 
38
    while i < n:
 
39
        c = pat[i]
 
40
        i += 1
 
41
        if c == '*':
 
42
            if pat[i:i+1] == '*': # pattern '**'
 
43
                res = res + '.*'
 
44
                i += 1
 
45
            else: # pattern '*'
 
46
                res = res + r'[^/\\]*'
 
47
        elif c == '?':
 
48
            res = res + r'[^/\\]'
 
49
        elif c == '[':
 
50
            j = i
 
51
            if j < n and pat[j] == '!':
 
52
                j = j+1
 
53
            if j < n and pat[j] == ']':
 
54
                j = j+1
 
55
            while j < n and pat[j] != ']':
 
56
                j = j+1
 
57
            if j >= n:
 
58
                res = res + '\\['
 
59
            else:
 
60
                stuff = pat[i:j].replace('\\','\\\\')
 
61
                i = j+1
 
62
                if stuff[0] == '!':
 
63
                    stuff = '^' + stuff[1:] + r'/\\'
 
64
                elif stuff[0] == '^':
 
65
                    stuff = '\\' + stuff
 
66
                res = '%s[%s]' % (res, stuff)
 
67
        else:
 
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
 
71
    # the entire string.
 
72
    return res + "$"
 
73
 
 
74
 
 
75
class _GlobMatcher(object):
 
76
    """A class which handles matching filenames to glob expressions"""
 
77
    
 
78
    def __init__(self, glob_re):
 
79
        """Create a matcher from a regular expression."""
 
80
        self._compiled_re = re.compile(glob_re, re.UNICODE)
 
81
 
 
82
    def __call__(self, fname):
 
83
        """See if fname matches the internal glob.
 
84
 
 
85
        :param fname: A filename to check.
 
86
        :return: Boolean, does/doesn't match
 
87
        """
 
88
        return self._compiled_re.match(fname) is not None
 
89
 
 
90
 
 
91
def glob_to_matcher(glob):
 
92
    """Return a callable which will match filenames versus the glob."""
 
93
    return _GlobMatcher(glob_to_re(glob))
 
94
 
 
95
 
 
96
def globs_to_re(patterns):
 
97
    """Convert a set of patterns into a single regular expression.
 
98
 
 
99
    :param patterns: A list of patterns to transform
 
100
    :return: A regular expression combining all patterns
 
101
    """
 
102
    final_re = []
 
103
    for pat in 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))$
 
110
        
 
111
        # TODO: jam 20060107 Is it more efficient to do:
 
112
        #       (pat1|pat2|pat3)$
 
113
        #       Or 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
 
117
 
 
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
 
121
    # for every pattern.
 
122
    # Just put one at the end
 
123
    return '(' + '|'.join(final_re) + ')$'
 
124
 
 
125
 
 
126
def globs_to_matcher(patterns):
 
127
    """Return a callable which will match filenames versus the globs."""
 
128
    return _GlobMatcher(globs_to_re(patterns))
 
129
 
 
130