~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/globbing.py

  • Committer: Martin Pool
  • Date: 2010-02-25 06:17:27 UTC
  • mfrom: (5055 +trunk)
  • mto: This revision was merged to the branch mainline in revision 5057.
  • Revision ID: mbp@sourcefrog.net-20100225061727-4sd9lt0qmdc6087t
merge news

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2011 Canonical Ltd
 
1
# Copyright (C) 2006-2010 Canonical Ltd
2
2
 
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
22
22
 
23
23
import re
24
24
 
25
 
from bzrlib import (
26
 
    errors,
27
 
    lazy_regex,
28
 
    )
29
25
from bzrlib.trace import (
30
 
    mutter,
31
 
    warning,
 
26
    warning
32
27
    )
33
28
 
34
29
 
41
36
    must not contain capturing groups.
42
37
    """
43
38
 
44
 
    _expand = lazy_regex.lazy_compile(ur'\\&')
 
39
    _expand = re.compile(ur'\\&')
45
40
 
46
41
    def __init__(self, source=None):
47
42
        self._pat = None
77
72
 
78
73
    def __call__(self, text):
79
74
        if not self._pat:
80
 
            self._pat = lazy_regex.lazy_compile(
 
75
            self._pat = re.compile(
81
76
                    u'|'.join([u'(%s)' % p for p in self._pats]),
82
77
                    re.UNICODE)
83
78
        return self._pat.sub(self._do_sub, text)
182
177
    so are matched first, then the basename patterns, then the fullpath
183
178
    patterns.
184
179
    """
185
 
    # We want to _add_patterns in a specific order (as per type_list below)
186
 
    # starting with the shortest and going to the longest.
187
 
    # As some Python version don't support ordered dicts the list below is
188
 
    # used to select inputs for _add_pattern in a specific order.
189
 
    pattern_types = [ "extension", "basename", "fullpath" ]
190
 
 
191
 
    pattern_info = {
192
 
        "extension" : {
193
 
            "translator" : _sub_extension,
194
 
            "prefix" : r'(?:.*/)?(?!.*/)(?:.*\.)'
195
 
        },
196
 
        "basename" : {
197
 
            "translator" : _sub_basename,
198
 
            "prefix" : r'(?:.*/)?(?!.*/)'
199
 
        },
200
 
        "fullpath" : {
201
 
            "translator" : _sub_fullpath,
202
 
            "prefix" : r''
203
 
        },
204
 
    }
205
 
 
206
180
    def __init__(self, patterns):
207
181
        self._regex_patterns = []
208
 
        pattern_lists = {
209
 
            "extension" : [],
210
 
            "basename" : [],
211
 
            "fullpath" : [],
212
 
        }
 
182
        path_patterns = []
 
183
        base_patterns = []
 
184
        ext_patterns = []
213
185
        for pat in patterns:
214
186
            pat = normalize_pattern(pat)
215
 
            pattern_lists[Globster.identify(pat)].append(pat)
216
 
        pi = Globster.pattern_info
217
 
        for t in Globster.pattern_types:
218
 
            self._add_patterns(pattern_lists[t], pi[t]["translator"],
219
 
                pi[t]["prefix"])
 
187
            if pat.startswith(u'RE:') or u'/' in pat:
 
188
                path_patterns.append(pat)
 
189
            elif pat.startswith(u'*.'):
 
190
                ext_patterns.append(pat)
 
191
            else:
 
192
                base_patterns.append(pat)
 
193
        self._add_patterns(ext_patterns,_sub_extension,
 
194
            prefix=r'(?:.*/)?(?!.*/)(?:.*\.)')
 
195
        self._add_patterns(base_patterns,_sub_basename,
 
196
            prefix=r'(?:.*/)?(?!.*/)')
 
197
        self._add_patterns(path_patterns,_sub_fullpath)
220
198
 
221
199
    def _add_patterns(self, patterns, translator, prefix=''):
222
200
        while patterns:
223
 
            grouped_rules = [
224
 
                '(%s)' % translator(pat) for pat in patterns[:99]]
 
201
            grouped_rules = ['(%s)' % translator(pat) for pat in patterns[:99]]
225
202
            joined_rule = '%s(?:%s)$' % (prefix, '|'.join(grouped_rules))
226
 
            # Explicitly use lazy_compile here, because we count on its
227
 
            # nicer error reporting.
228
 
            self._regex_patterns.append((
229
 
                lazy_regex.lazy_compile(joined_rule, re.UNICODE),
 
203
            self._regex_patterns.append((re.compile(joined_rule, re.UNICODE),
230
204
                patterns[:99]))
231
205
            patterns = patterns[99:]
232
206
 
235
209
 
236
210
        :return A matching pattern or None if there is no matching pattern.
237
211
        """
238
 
        try:
239
 
            for regex, patterns in self._regex_patterns:
240
 
                match = regex.match(filename)
241
 
                if match:
242
 
                    return patterns[match.lastindex -1]
243
 
        except errors.InvalidPattern, e:
244
 
            # We can't show the default e.msg to the user as thats for
245
 
            # the combined pattern we sent to regex. Instead we indicate to
246
 
            # the user that an ignore file needs fixing.
247
 
            mutter('Invalid pattern found in regex: %s.', e.msg)
248
 
            e.msg = "File ~/.bazaar/ignore or .bzrignore contains error(s)."
249
 
            bad_patterns = ''
250
 
            for _, patterns in self._regex_patterns:
251
 
                for p in patterns:
252
 
                    if not Globster.is_pattern_valid(p):
253
 
                        bad_patterns += ('\n  %s' % p)
254
 
            e.msg += bad_patterns
255
 
            raise e
 
212
        for regex, patterns in self._regex_patterns:
 
213
            match = regex.match(filename)
 
214
            if match:
 
215
                return patterns[match.lastindex -1]
256
216
        return None
257
217
 
258
 
    @staticmethod
259
 
    def identify(pattern):
260
 
        """Returns pattern category.
261
 
 
262
 
        :param pattern: normalized pattern.
263
 
        Identify if a pattern is fullpath, basename or extension
264
 
        and returns the appropriate type.
265
 
        """
266
 
        if pattern.startswith(u'RE:') or u'/' in pattern:
267
 
            return "fullpath"
268
 
        elif pattern.startswith(u'*.'):
269
 
            return "extension"
270
 
        else:
271
 
            return "basename"
272
 
 
273
 
    @staticmethod
274
 
    def is_pattern_valid(pattern):
275
 
        """Returns True if pattern is valid.
276
 
 
277
 
        :param pattern: Normalized pattern.
278
 
        is_pattern_valid() assumes pattern to be normalized.
279
 
        see: globbing.normalize_pattern
280
 
        """
281
 
        result = True
282
 
        translator = Globster.pattern_info[Globster.identify(pattern)]["translator"]
283
 
        tpattern = '(%s)' % translator(pattern)
284
 
        try:
285
 
            re_obj = lazy_regex.lazy_compile(tpattern, re.UNICODE)
286
 
            re_obj.search("") # force compile
287
 
        except errors.InvalidPattern, e:
288
 
            result = False
289
 
        return result
290
 
 
291
 
 
292
218
class ExceptionGlobster(object):
293
219
    """A Globster that supports exception patterns.
294
220
    
336
262
        self._regex_patterns = []
337
263
        for pat in patterns:
338
264
            pat = normalize_pattern(pat)
339
 
            t = Globster.identify(pat)
340
 
            self._add_patterns([pat], Globster.pattern_info[t]["translator"],
341
 
                Globster.pattern_info[t]["prefix"])
342
 
 
343
 
 
344
 
_slashes = lazy_regex.lazy_compile(r'[\\/]+')
 
265
            if pat.startswith(u'RE:') or u'/' in pat:
 
266
                self._add_patterns([pat], _sub_fullpath)
 
267
            elif pat.startswith(u'*.'):
 
268
                self._add_patterns([pat], _sub_extension,
 
269
                    prefix=r'(?:.*/)?(?!.*/)(?:.*\.)')
 
270
            else:
 
271
                self._add_patterns([pat], _sub_basename,
 
272
                    prefix=r'(?:.*/)?(?!.*/)')
 
273
 
 
274
 
 
275
_slashes = re.compile(r'[\\/]+')
345
276
def normalize_pattern(pattern):
346
277
    """Converts backslashes in path patterns to forward slashes.
347
278