/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/globbing.py

  • Committer: Robert Collins
  • Date: 2010-05-06 11:08:10 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506110810-h3j07fh5gmw54s25
Cleaner matcher matching revised unlocking protocol.

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
20
20
expressions.
21
21
"""
22
22
 
23
 
from __future__ import absolute_import
24
 
 
25
23
import re
26
24
 
27
 
from . import (
28
 
    lazy_regex,
29
 
    )
30
 
from .trace import (
31
 
    mutter,
32
 
    warning,
 
25
from bzrlib.trace import (
 
26
    warning
33
27
    )
34
28
 
35
29
 
42
36
    must not contain capturing groups.
43
37
    """
44
38
 
45
 
    _expand = lazy_regex.lazy_compile(u'\\\\&')
 
39
    _expand = re.compile(ur'\\&')
46
40
 
47
41
    def __init__(self, source=None):
48
42
        self._pat = None
78
72
 
79
73
    def __call__(self, text):
80
74
        if not self._pat:
81
 
            self._pat = lazy_regex.lazy_compile(
82
 
                u'|'.join([u'(%s)' % p for p in self._pats]),
83
 
                re.UNICODE)
 
75
            self._pat = re.compile(
 
76
                    u'|'.join([u'(%s)' % p for p in self._pats]),
 
77
                    re.UNICODE)
84
78
        return self._pat.sub(self._do_sub, text)
85
79
 
86
80
    def _do_sub(self, m):
92
86
 
93
87
 
94
88
_sub_named = Replacer()
95
 
_sub_named.add(r'\[:digit:\]', r'\d')
96
 
_sub_named.add(r'\[:space:\]', r'\s')
97
 
_sub_named.add(r'\[:alnum:\]', r'\w')
98
 
_sub_named.add(r'\[:ascii:\]', r'\0-\x7f')
99
 
_sub_named.add(r'\[:blank:\]', r' \t')
100
 
_sub_named.add(r'\[:cntrl:\]', r'\0-\x1f\x7f-\x9f')
 
89
_sub_named.add(ur'\[:digit:\]', ur'\d')
 
90
_sub_named.add(ur'\[:space:\]', ur'\s')
 
91
_sub_named.add(ur'\[:alnum:\]', ur'\w')
 
92
_sub_named.add(ur'\[:ascii:\]', ur'\0-\x7f')
 
93
_sub_named.add(ur'\[:blank:\]', ur' \t')
 
94
_sub_named.add(ur'\[:cntrl:\]', ur'\0-\x1f\x7f-\x9f')
101
95
 
102
96
 
103
97
def _sub_group(m):
129
123
 
130
124
_sub_re = Replacer()
131
125
_sub_re.add(u'^RE:', u'')
132
 
_sub_re.add(u'\\((?!\\?)', u'(?:')
133
 
_sub_re.add(u'\\(\\?P<.*>', _invalid_regex(u'(?:'))
134
 
_sub_re.add(u'\\(\\?P=[^)]*\\)', _invalid_regex(u''))
135
 
_sub_re.add(r'\\+$', _trailing_backslashes_regex)
 
126
_sub_re.add(u'\((?!\?)', u'(?:')
 
127
_sub_re.add(u'\(\?P<.*>', _invalid_regex(u'(?:'))
 
128
_sub_re.add(u'\(\?P=[^)]*\)', _invalid_regex(u''))
 
129
_sub_re.add(ur'\\+$', _trailing_backslashes_regex)
136
130
 
137
131
 
138
132
_sub_fullpath = Replacer()
139
 
_sub_fullpath.add(r'^RE:.*', _sub_re)  # RE:<anything> is a regex
140
 
_sub_fullpath.add(r'\[\^?\]?(?:[^][]|\[:[^]]+:\])+\]',
141
 
                  _sub_group)  # char group
142
 
_sub_fullpath.add(r'(?:(?<=/)|^)(?:\.?/)+', u'')  # canonicalize path
143
 
_sub_fullpath.add(r'\\.', r'\&')  # keep anything backslashed
144
 
_sub_fullpath.add(r'[(){}|^$+.]', r'\\&')  # escape specials
145
 
_sub_fullpath.add(r'(?:(?<=/)|^)\*\*+/', r'(?:.*/)?')  # **/ after ^ or /
146
 
_sub_fullpath.add(r'\*+', r'[^/]*')  # * elsewhere
147
 
_sub_fullpath.add(r'\?', r'[^/]')  # ? everywhere
 
133
_sub_fullpath.add(ur'^RE:.*', _sub_re) # RE:<anything> is a regex
 
134
_sub_fullpath.add(ur'\[\^?\]?(?:[^][]|\[:[^]]+:\])+\]', _sub_group) # char group
 
135
_sub_fullpath.add(ur'(?:(?<=/)|^)(?:\.?/)+', u'') # canonicalize path
 
136
_sub_fullpath.add(ur'\\.', ur'\&') # keep anything backslashed
 
137
_sub_fullpath.add(ur'[(){}|^$+.]', ur'\\&') # escape specials
 
138
_sub_fullpath.add(ur'(?:(?<=/)|^)\*\*+/', ur'(?:.*/)?') # **/ after ^ or /
 
139
_sub_fullpath.add(ur'\*+', ur'[^/]*') # * elsewhere
 
140
_sub_fullpath.add(ur'\?', ur'[^/]') # ? everywhere
148
141
 
149
142
 
150
143
_sub_basename = Replacer()
151
 
_sub_basename.add(r'\[\^?\]?(?:[^][]|\[:[^]]+:\])+\]',
152
 
                  _sub_group)  # char group
153
 
_sub_basename.add(r'\\.', r'\&')  # keep anything backslashed
154
 
_sub_basename.add(r'[(){}|^$+.]', r'\\&')  # escape specials
155
 
_sub_basename.add(r'\*+', r'.*')  # * everywhere
156
 
_sub_basename.add(r'\?', r'.')  # ? everywhere
 
144
_sub_basename.add(ur'\[\^?\]?(?:[^][]|\[:[^]]+:\])+\]', _sub_group) # char group
 
145
_sub_basename.add(ur'\\.', ur'\&') # keep anything backslashed
 
146
_sub_basename.add(ur'[(){}|^$+.]', ur'\\&') # escape specials
 
147
_sub_basename.add(ur'\*+', ur'.*') # * everywhere
 
148
_sub_basename.add(ur'\?', ur'.') # ? everywhere
157
149
 
158
150
 
159
151
def _sub_extension(pattern):
185
177
    so are matched first, then the basename patterns, then the fullpath
186
178
    patterns.
187
179
    """
188
 
    # We want to _add_patterns in a specific order (as per type_list below)
189
 
    # starting with the shortest and going to the longest.
190
 
    # As some Python version don't support ordered dicts the list below is
191
 
    # used to select inputs for _add_pattern in a specific order.
192
 
    pattern_types = ["extension", "basename", "fullpath"]
193
 
 
194
 
    pattern_info = {
195
 
        "extension": {
196
 
            "translator": _sub_extension,
197
 
            "prefix": r'(?:.*/)?(?!.*/)(?:.*\.)'
198
 
        },
199
 
        "basename": {
200
 
            "translator": _sub_basename,
201
 
            "prefix": r'(?:.*/)?(?!.*/)'
202
 
        },
203
 
        "fullpath": {
204
 
            "translator": _sub_fullpath,
205
 
            "prefix": r''
206
 
        },
207
 
    }
208
 
 
209
180
    def __init__(self, patterns):
210
181
        self._regex_patterns = []
211
 
        pattern_lists = {
212
 
            "extension": [],
213
 
            "basename": [],
214
 
            "fullpath": [],
215
 
        }
 
182
        path_patterns = []
 
183
        base_patterns = []
 
184
        ext_patterns = []
216
185
        for pat in patterns:
217
186
            pat = normalize_pattern(pat)
218
 
            pattern_lists[Globster.identify(pat)].append(pat)
219
 
        pi = Globster.pattern_info
220
 
        for t in Globster.pattern_types:
221
 
            self._add_patterns(pattern_lists[t], pi[t]["translator"],
222
 
                               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)
223
198
 
224
199
    def _add_patterns(self, patterns, translator, prefix=''):
225
200
        while patterns:
226
 
            grouped_rules = [
227
 
                '(%s)' % translator(pat) for pat in patterns[:99]]
 
201
            grouped_rules = ['(%s)' % translator(pat) for pat in patterns[:99]]
228
202
            joined_rule = '%s(?:%s)$' % (prefix, '|'.join(grouped_rules))
229
 
            # Explicitly use lazy_compile here, because we count on its
230
 
            # nicer error reporting.
231
 
            self._regex_patterns.append((
232
 
                lazy_regex.lazy_compile(joined_rule, re.UNICODE),
 
203
            self._regex_patterns.append((re.compile(joined_rule, re.UNICODE),
233
204
                patterns[:99]))
234
205
            patterns = patterns[99:]
235
206
 
238
209
 
239
210
        :return A matching pattern or None if there is no matching pattern.
240
211
        """
241
 
        try:
242
 
            for regex, patterns in self._regex_patterns:
243
 
                match = regex.match(filename)
244
 
                if match:
245
 
                    return patterns[match.lastindex - 1]
246
 
        except lazy_regex.InvalidPattern as e:
247
 
            # We can't show the default e.msg to the user as thats for
248
 
            # the combined pattern we sent to regex. Instead we indicate to
249
 
            # the user that an ignore file needs fixing.
250
 
            mutter('Invalid pattern found in regex: %s.', e.msg)
251
 
            e.msg = "File ~/.bazaar/ignore or .bzrignore contains error(s)."
252
 
            bad_patterns = ''
253
 
            for _, patterns in self._regex_patterns:
254
 
                for p in patterns:
255
 
                    if not Globster.is_pattern_valid(p):
256
 
                        bad_patterns += ('\n  %s' % p)
257
 
            e.msg += bad_patterns
258
 
            raise e
 
212
        for regex, patterns in self._regex_patterns:
 
213
            match = regex.match(filename)
 
214
            if match:
 
215
                return patterns[match.lastindex -1]
259
216
        return None
260
217
 
261
 
    @staticmethod
262
 
    def identify(pattern):
263
 
        """Returns pattern category.
264
 
 
265
 
        :param pattern: normalized pattern.
266
 
        Identify if a pattern is fullpath, basename or extension
267
 
        and returns the appropriate type.
268
 
        """
269
 
        if pattern.startswith(u'RE:') or u'/' in pattern:
270
 
            return "fullpath"
271
 
        elif pattern.startswith(u'*.'):
272
 
            return "extension"
273
 
        else:
274
 
            return "basename"
275
 
 
276
 
    @staticmethod
277
 
    def is_pattern_valid(pattern):
278
 
        """Returns True if pattern is valid.
279
 
 
280
 
        :param pattern: Normalized pattern.
281
 
        is_pattern_valid() assumes pattern to be normalized.
282
 
        see: globbing.normalize_pattern
283
 
        """
284
 
        result = True
285
 
        translator = Globster.pattern_info[Globster.identify(
286
 
            pattern)]["translator"]
287
 
        tpattern = '(%s)' % translator(pattern)
288
 
        try:
289
 
            re_obj = lazy_regex.lazy_compile(tpattern, re.UNICODE)
290
 
            re_obj.search("")  # force compile
291
 
        except lazy_regex.InvalidPattern:
292
 
            result = False
293
 
        return result
294
 
 
295
 
 
296
218
class ExceptionGlobster(object):
297
219
    """A Globster that supports exception patterns.
298
 
 
 
220
    
299
221
    Exceptions are ignore patterns prefixed with '!'.  Exception
300
 
    patterns take precedence over regular patterns and cause a
301
 
    matching filename to return None from the match() function.
302
 
    Patterns using a '!!' prefix are highest precedence, and act
 
222
    patterns take precedence over regular patterns and cause a 
 
223
    matching filename to return None from the match() function.  
 
224
    Patterns using a '!!' prefix are highest precedence, and act 
303
225
    as regular ignores. '!!' patterns are useful to establish ignores
304
226
    that apply under paths specified by '!' exception patterns.
305
227
    """
306
 
 
307
 
    def __init__(self, patterns):
 
228
    
 
229
    def __init__(self,patterns):
308
230
        ignores = [[], [], []]
309
231
        for p in patterns:
310
232
            if p.startswith(u'!!'):
314
236
            else:
315
237
                ignores[0].append(p)
316
238
        self._ignores = [Globster(i) for i in ignores]
317
 
 
 
239
        
318
240
    def match(self, filename):
319
241
        """Searches for a pattern that matches the given filename.
320
242
 
328
250
        else:
329
251
            return self._ignores[0].match(filename)
330
252
 
331
 
 
332
253
class _OrderedGlobster(Globster):
333
254
    """A Globster that keeps pattern order."""
334
255
 
341
262
        self._regex_patterns = []
342
263
        for pat in patterns:
343
264
            pat = normalize_pattern(pat)
344
 
            t = Globster.identify(pat)
345
 
            self._add_patterns([pat], Globster.pattern_info[t]["translator"],
346
 
                               Globster.pattern_info[t]["prefix"])
347
 
 
348
 
 
349
 
_slashes = lazy_regex.lazy_compile(r'[\\/]+')
350
 
 
351
 
 
 
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'[\\/]+')
352
276
def normalize_pattern(pattern):
353
277
    """Converts backslashes in path patterns to forward slashes.
354
278