177
182
    so are matched first, then the basename patterns, then the fullpath
 
 
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" ]
 
 
193
            "translator" : _sub_extension,
 
 
194
            "prefix" : r'(?:.*/)?(?!.*/)(?:.*\.)'
 
 
197
            "translator" : _sub_basename,
 
 
198
            "prefix" : r'(?:.*/)?(?!.*/)'
 
 
201
            "translator" : _sub_fullpath,
 
180
206
    def __init__(self, patterns):
 
181
207
        self._regex_patterns = []
 
185
213
        for pat in patterns:
 
186
214
            pat = normalize_pattern(pat)
 
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)
 
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)
 
 
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"],
 
199
221
    def _add_patterns(self, patterns, translator, prefix=''):
 
201
 
            grouped_rules = ['(%s)' % translator(pat) for pat in patterns[:99]]
 
 
224
                '(%s)' % translator(pat) for pat in patterns[:99]]
 
202
225
            joined_rule = '%s(?:%s)$' % (prefix, '|'.join(grouped_rules))
 
203
 
            self._regex_patterns.append((re.compile(joined_rule, re.UNICODE),
 
 
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),
 
205
231
            patterns = patterns[99:]
 
 
210
236
        :return A matching pattern or None if there is no matching pattern.
 
212
 
        for regex, patterns in self._regex_patterns:
 
213
 
            match = regex.match(filename)
 
215
 
                return patterns[match.lastindex -1]
 
 
239
            for regex, patterns in self._regex_patterns:
 
 
240
                match = regex.match(filename)
 
 
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)."
 
 
250
            for _, patterns in self._regex_patterns:
 
 
252
                    if not Globster.is_pattern_valid(p):
 
 
253
                        bad_patterns += ('\n  %s' % p)
 
 
254
            e.msg += bad_patterns
 
 
259
    def identify(pattern):
 
 
260
        """Returns pattern category.
 
 
262
        :param pattern: normalized pattern.
 
 
263
        Identify if a pattern is fullpath, basename or extension
 
 
264
        and returns the appropriate type.
 
 
266
        if pattern.startswith(u'RE:') or u'/' in pattern:
 
 
268
        elif pattern.startswith(u'*.'):
 
 
274
    def is_pattern_valid(pattern):
 
 
275
        """Returns True if pattern is valid.
 
 
277
        :param pattern: Normalized pattern.
 
 
278
        is_pattern_valid() assumes pattern to be normalized.
 
 
279
        see: globbing.normalize_pattern
 
 
282
        translator = Globster.pattern_info[Globster.identify(pattern)]["translator"]
 
 
283
        tpattern = '(%s)' % translator(pattern)
 
 
285
            re_obj = lazy_regex.lazy_compile(tpattern, re.UNICODE)
 
 
286
            re_obj.search("") # force compile
 
 
287
        except errors.InvalidPattern, e:
 
218
292
class ExceptionGlobster(object):
 
219
293
    """A Globster that supports exception patterns.
 
 
262
336
        self._regex_patterns = []
 
263
337
        for pat in patterns:
 
264
338
            pat = normalize_pattern(pat)
 
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'(?:.*/)?(?!.*/)(?:.*\.)')
 
271
 
                self._add_patterns([pat], _sub_basename,
 
272
 
                    prefix=r'(?:.*/)?(?!.*/)')
 
275
 
_slashes = re.compile(r'[\\/]+')
 
 
339
            t = Globster.identify(pat)
 
 
340
            self._add_patterns([pat], Globster.pattern_info[t]["translator"],
 
 
341
                Globster.pattern_info[t]["prefix"])
 
 
344
_slashes = lazy_regex.lazy_compile(r'[\\/]+')
 
276
345
def normalize_pattern(pattern):
 
277
346
    """Converts backslashes in path patterns to forward slashes.