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