/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/glob.py

  • Committer: Kent Gibson
  • Date: 2006-11-28 17:42:31 UTC
  • mto: (2178.1.1 jam-integration)
  • mto: This revision was merged to the branch mainline in revision 2179.
  • Revision ID: warthog618@gmail.com-20061128174231-b603629ca23ff1bc
Update ignore help to remove case-insensitive comment.

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
"""Tools for converting globs to regular expressions.
 
18
 
 
19
This module provides functions for converting shell-like globs to regular
 
20
expressions.
 
21
"""
 
22
 
 
23
import re
 
24
import os.path
 
25
 
 
26
from bzrlib.trace import (
 
27
    mutter, 
 
28
    warning
 
29
    )
 
30
 
 
31
 
 
32
class Replacer(object):
 
33
    """Do a multiple-pattern substitution.
 
34
 
 
35
    The patterns and substitutions are combined into one, so the result of
 
36
    one replacement is never substituted again. Add the patterns and
 
37
    replacements via the add method and then call the object. The patterns
 
38
    must not contain capturing groups.
 
39
    """
 
40
 
 
41
    _expand = re.compile(ur'\\&')
 
42
 
 
43
    def __init__(self, source=None):
 
44
        self._pat = None
 
45
        if source:
 
46
            self._pats = list(source._pats)
 
47
            self._funs = list(source._funs)
 
48
        else:
 
49
            self._pats = []
 
50
            self._funs = []
 
51
 
 
52
    def add(self, pat, fun):
 
53
        r"""Add a pattern and replacement.
 
54
 
 
55
        The pattern must not contain capturing groups.
 
56
        The replacement might be either a string template in which \& will be
 
57
        replaced with the match, or a function that will get the matching text  
 
58
        as argument. It does not get match object, because capturing is 
 
59
        forbidden anyway.
 
60
        """
 
61
        self._pat = None
 
62
        self._pats.append(pat)
 
63
        self._funs.append(fun)
 
64
 
 
65
    def add_replacer(self, replacer):
 
66
        r"""Add all patterns from another replacer.
 
67
 
 
68
        All patterns and replacements from replacer are appended to the ones
 
69
        already defined.
 
70
        """
 
71
        self._pat = None
 
72
        self._pats.extend(replacer._pats)
 
73
        self._funs.extend(replacer._funs)
 
74
 
 
75
    def __call__(self, text):
 
76
        if not self._pat:
 
77
            self._pat = re.compile(
 
78
                    u'|'.join([u'(%s)' % p for p in self._pats]),
 
79
                    re.UNICODE)
 
80
        return self._pat.sub(self._do_sub, text)
 
81
 
 
82
    def _do_sub(self, m):
 
83
        fun = self._funs[m.lastindex - 1]
 
84
        if hasattr(fun, '__call__'):
 
85
            return fun(m.group(0))
 
86
        else:
 
87
            return self._expand.sub(m.group(0), fun)
 
88
 
 
89
 
 
90
_sub_named = Replacer()
 
91
_sub_named.add(ur'\[:digit:\]', ur'\d')
 
92
_sub_named.add(ur'\[:space:\]', ur'\s')
 
93
_sub_named.add(ur'\[:alnum:\]', ur'\w')
 
94
_sub_named.add(ur'\[:ascii:\]', ur'\0-\x7f')
 
95
_sub_named.add(ur'\[:blank:\]', ur' \t')
 
96
_sub_named.add(ur'\[:cntrl:\]', ur'\0-\x1f\x7f-\x9f')
 
97
 
 
98
 
 
99
def _sub_group(m):
 
100
    if m[1] in (u'!', u'^'):
 
101
        return u'[^' + _sub_named(m[2:-1]) + u']'
 
102
    return u'[' + _sub_named(m[1:-1]) + u']'
 
103
 
 
104
 
 
105
def _invalid_regex(repl):
 
106
    def _(m):
 
107
        warning(u"'%s' not allowed withing regexp. Replacing with '%s'" %
 
108
                (m, repl))
 
109
        return repl
 
110
    return _
 
111
 
 
112
 
 
113
_sub_re = Replacer()
 
114
_sub_re.add(u'^RE:', u'')
 
115
_sub_re.add(u'\((?!\?)', u'(?:')
 
116
_sub_re.add(u'\(\?P<.*>', _invalid_regex(u'(?:'))
 
117
_sub_re.add(u'\(\?P=[^)]*\)', _invalid_regex(u''))
 
118
 
 
119
 
 
120
_sub_fullpath = Replacer()
 
121
_sub_fullpath.add(ur'^RE:.*', _sub_re) # RE:<anything> is a regex
 
122
_sub_fullpath.add(ur'\[\^?\]?(?:[^][]|\[:[^]]+:\])+\]', _sub_group) # char group
 
123
_sub_fullpath.add(ur'(?:(?<=/)|^)(?:\.?/)+', u'') # canonicalize path
 
124
_sub_fullpath.add(ur'\\.', ur'\&') # keep anything backslashed
 
125
_sub_fullpath.add(ur'[(){}|^$+.]', ur'\\&') # escape specials
 
126
_sub_fullpath.add(ur'(?:(?<=/)|^)\*\*+/', ur'(?:.*/)?') # **/ after ^ or /
 
127
_sub_fullpath.add(ur'\*+', ur'[^/]*') # * elsewhere
 
128
_sub_fullpath.add(ur'\?', ur'[^/]') # ? everywhere
 
129
 
 
130
 
 
131
_sub_basename = Replacer()
 
132
_sub_basename.add(ur'\[\^?\]?(?:[^][]|\[:[^]]+:\])+\]', _sub_group) # char group
 
133
_sub_basename.add(ur'\\.', ur'\&') # keep anything backslashed
 
134
_sub_basename.add(ur'[(){}|^$+.]', ur'\\&') # escape specials
 
135
_sub_basename.add(ur'\*+', ur'.*') # * everywhere
 
136
_sub_basename.add(ur'\?', ur'.') # ? everywhere
 
137
 
 
138
 
 
139
def _sub_extension(pattern):
 
140
    return _sub_basename(pattern[2:])
 
141
 
 
142
 
 
143
class Globster(object):
 
144
    """A simple wrapper for a set of glob patterns.
 
145
 
 
146
    Provides the capability to search the patterns to find a match for
 
147
    a given filename (including the full path).
 
148
 
 
149
    Patterns are translated to regular expressions to expidite matching.
 
150
 
 
151
    The regular expressions for multiple patterns are aggregated into 
 
152
    a super-regex containing groups of up to 99 patterns.  
 
153
    The 99 limitation is due to the grouping limit of the Python re module.
 
154
    The resulting super-regex and associated patterns are stored as a list of
 
155
    (regex,[patterns]) in _regex_patterns.
 
156
    
 
157
    For performance reasons the patterns are categorised as extension patterns
 
158
    (those that match against a file extension), basename patterns
 
159
    (those that match against the basename of the filename),
 
160
    and fullpath patterns (those that match against the full path).
 
161
    The translations used for extensions and basenames are relatively simpler 
 
162
    and therefore faster to perform than the fullpath patterns.
 
163
 
 
164
    Also, the extension patterns are more likely to find a match and 
 
165
    so are matched first, then the basename patterns, then the fullpath
 
166
    patterns.
 
167
    """
 
168
    def __init__(self, patterns):
 
169
        self._regex_patterns = []
 
170
        path_patterns = []
 
171
        base_patterns = []
 
172
        ext_patterns = []
 
173
        for pat in patterns:
 
174
            if pat.startswith(u'RE:') or u'/' in pat:
 
175
                path_patterns.append(pat)
 
176
            elif pat.startswith(u'*.'):
 
177
                ext_patterns.append(pat)
 
178
            else:
 
179
                base_patterns.append(pat)
 
180
        self._add_patterns(ext_patterns,_sub_extension,
 
181
            prefix=r'(?:.*/)?(?!.*/)(?:.*\.)')
 
182
        self._add_patterns(base_patterns,_sub_basename, 
 
183
            prefix=r'(?:.*/)?(?!.*/)')
 
184
        self._add_patterns(path_patterns,_sub_fullpath) 
 
185
 
 
186
    def _add_patterns(self, patterns, translator, prefix=''):
 
187
        while patterns:
 
188
            grouped_rules = ['(%s)' % translator(pat) for pat in patterns[:99]]
 
189
            joined_rule = '%s(?:%s)$' % (prefix, '|'.join(grouped_rules))
 
190
            self._regex_patterns.append((re.compile(joined_rule, re.UNICODE), 
 
191
                patterns[:99]))
 
192
            patterns = patterns[99:]
 
193
 
 
194
    def match(self, filename):
 
195
        """Searches for a pattern that matches the given filename.
 
196
        
 
197
        :return A matching pattern or None if there is no matching pattern.
 
198
        """
 
199
        for regex, patterns in self._regex_patterns:
 
200
            match = regex.match(filename)
 
201
            if match:
 
202
                return patterns[match.lastindex -1]
 
203
        return None
 
204