Use isinstance() instead of comparing types directly.
[cvs2svn.git] / cvs2svn_lib / symbol_transform.py
blob21a853b922cbf9fc5ef8f5151c4387758577b840
1 # (Be in -*- python -*- mode.)
3 # ====================================================================
4 # Copyright (c) 2006-2009 CollabNet. All rights reserved.
6 # This software is licensed as described in the file COPYING, which
7 # you should have received as part of this distribution. The terms
8 # are also available at http://subversion.tigris.org/license-1.html.
9 # If newer versions of this license are posted there, you may use a
10 # newer version instead, at your option.
12 # This software consists of voluntary contributions made by many
13 # individuals. For exact contribution history, see the revision
14 # history and logs, available at http://cvs2svn.tigris.org/.
15 # ====================================================================
17 """This module contains classes to transform symbol names."""
20 import os
21 import re
23 from cvs2svn_lib.log import Log
24 from cvs2svn_lib.common import FatalError
25 from cvs2svn_lib.common import IllegalSVNPathError
26 from cvs2svn_lib.common import normalize_svn_path
29 class SymbolTransform:
30 """Transform symbol names arbitrarily."""
32 def transform(self, cvs_file, symbol_name, revision):
33 """Possibly transform SYMBOL_NAME, which was found in CVS_FILE.
35 Return the transformed symbol name. If this SymbolTransform
36 doesn't apply, return the original SYMBOL_NAME. If this symbol
37 should be ignored entirely, return None. (Please note that
38 ignoring a branch via this mechanism only causes the branch *name*
39 to be ignored; the branch contents will still be converted.
40 Usually branches should be excluded using --exclude.)
42 REVISION contains the CVS revision number to which the symbol was
43 attached in the file as a string (with zeros removed).
45 This method is free to use the information in CVS_FILE (including
46 CVS_FILE.project) to decide whether and/or how to transform
47 SYMBOL_NAME."""
49 raise NotImplementedError()
52 class ReplaceSubstringsSymbolTransform(SymbolTransform):
53 """Replace specific substrings in symbol names.
55 If the substring occurs multiple times, replace all copies."""
57 def __init__(self, old, new):
58 self.old = old
59 self.new = new
61 def transform(self, cvs_file, symbol_name, revision):
62 return symbol_name.replace(self.old, self.new)
65 class NormalizePathsSymbolTransform(SymbolTransform):
66 def transform(self, cvs_file, symbol_name, revision):
67 try:
68 return normalize_svn_path(symbol_name)
69 except IllegalSVNPathError, e:
70 raise FatalError('Problem with %s: %s' % (symbol_name, e,))
73 class CompoundSymbolTransform(SymbolTransform):
74 """A SymbolTransform that applies other SymbolTransforms in series.
76 Each of the contained SymbolTransforms is applied, one after the
77 other. If any of them returns None, then None is returned (the
78 following SymbolTransforms are ignored)."""
80 def __init__(self, symbol_transforms):
81 """Ininitialize a CompoundSymbolTransform.
83 SYMBOL_TRANSFORMS is an iterable of SymbolTransform instances."""
85 self.symbol_transforms = list(symbol_transforms)
87 def transform(self, cvs_file, symbol_name, revision):
88 for symbol_transform in self.symbol_transforms:
89 symbol_name = symbol_transform.transform(
90 cvs_file, symbol_name, revision
92 if symbol_name is None:
93 # Don't continue with other symbol transforms:
94 break
96 return symbol_name
99 class RegexpSymbolTransform(SymbolTransform):
100 """Transform symbols by using a regexp textual substitution."""
102 def __init__(self, pattern, replacement):
103 """Create a SymbolTransform that transforms symbols matching PATTERN.
105 PATTERN is a regular expression that should match the whole symbol
106 name. REPLACEMENT is the replacement text, which may include
107 patterns like r'\1' or r'\g<1>' or r'\g<name>' (where 'name' is a
108 reference to a named substring in the pattern of the form
109 r'(?P<name>...)')."""
111 self.pattern = re.compile('^' + pattern + '$')
112 self.replacement = replacement
114 def transform(self, cvs_file, symbol_name, revision):
115 return self.pattern.sub(self.replacement, symbol_name)
118 class SymbolMapper(SymbolTransform):
119 """A SymbolTransform that transforms specific symbol definitions.
121 The user has to specify the exact CVS filename, symbol name, and
122 revision number to be transformed, and the new name (or None if the
123 symbol should be ignored). The mappings can be set via a
124 constructor argument or by calling __setitem__()."""
126 def __init__(self, items=[]):
127 """Initialize the mapper.
129 ITEMS is a list of tuples (cvs_filename, symbol_name, revision,
130 new_name) which will be set as mappings."""
132 # A map {(cvs_filename, symbol_name, revision) : new_name}:
133 self._map = {}
135 for (cvs_filename, symbol_name, revision, new_name) in items:
136 self[cvs_filename, symbol_name, revision] = new_name
138 def __setitem__(self, (cvs_filename, symbol_name, revision), new_name):
139 """Set a mapping for a particular file, symbol, and revision."""
141 cvs_filename = os.path.normcase(os.path.normpath(cvs_filename))
142 key = (cvs_filename, symbol_name, revision)
143 if key in self._map:
144 Log().warn(
145 'Overwriting symbol transform for\n'
146 ' filename=%r symbol=%s revision=%s'
147 % (cvs_filename, symbol_name, revision,)
149 self._map[key] = new_name
151 def transform(self, cvs_file, symbol_name, revision):
152 cvs_filename = os.path.normcase(os.path.normpath(cvs_file.filename))
153 return self._map.get(
154 (cvs_filename, symbol_name, revision), symbol_name
158 class SubtreeSymbolMapper(SymbolTransform):
159 """A SymbolTransform that transforms symbols within a whole repo subtree.
161 The user has to specify a CVS repository path (a filename or
162 directory) and the original symbol name. All symbols under that
163 path will be renamed to the specified new name (which can be None if
164 the symbol should be ignored). The mappings can be set via a
165 constructor argument or by calling __setitem__(). Only the most
166 specific rule is applied."""
168 def __init__(self, items=[]):
169 """Initialize the mapper.
171 ITEMS is a list of tuples (cvs_path, symbol_name, new_name)
172 which will be set as mappings. cvs_path is a string naming a
173 directory within the CVS repository."""
175 # A map {symbol_name : {cvs_path : new_name}}:
176 self._map = {}
178 for (cvs_path, symbol_name, new_name) in items:
179 self[cvs_path, symbol_name] = new_name
181 def __setitem__(self, (cvs_path, symbol_name), new_name):
182 """Set a mapping for a particular file and symbol."""
184 try:
185 symbol_map = self._map[symbol_name]
186 except KeyError:
187 symbol_map = {}
188 self._map[symbol_name] = symbol_map
190 cvs_path = os.path.normcase(os.path.normpath(cvs_path))
191 if cvs_path in symbol_map:
192 Log().warn(
193 'Overwriting symbol transform for\n'
194 ' directory=%r symbol=%s'
195 % (cvs_path, symbol_name,)
197 symbol_map[cvs_path] = new_name
199 def transform(self, cvs_file, symbol_name, revision):
200 try:
201 symbol_map = self._map[symbol_name]
202 except KeyError:
203 # No rules for that symbol name
204 return symbol_name
206 cvs_path = os.path.normcase(os.path.normpath(cvs_file.filename))
207 while True:
208 try:
209 return symbol_map[cvs_path]
210 except KeyError:
211 new_cvs_path = os.path.dirname(cvs_path)
212 if new_cvs_path == cvs_path:
213 # No rules found for that path; return symbol name unaltered.
214 return symbol_name
215 else:
216 cvs_path = new_cvs_path
219 class IgnoreSymbolTransform(SymbolTransform):
220 """Ignore symbols matching a specified regular expression."""
222 def __init__(self, pattern):
223 """Create an SymbolTransform that ignores symbols matching PATTERN.
225 PATTERN is a regular expression that should match the whole symbol
226 name."""
228 self.pattern = re.compile('^' + pattern + '$')
230 def transform(self, cvs_file, symbol_name, revision):
231 if self.pattern.match(symbol_name):
232 return None
233 else:
234 return symbol_name
237 class SubtreeSymbolTransform(SymbolTransform):
238 """A wrapper around another SymbolTransform, that limits it to a
239 specified subtree."""
241 def __init__(self, cvs_path, inner_symbol_transform):
242 """Constructor.
244 CVS_PATH is the path in the repository. INNER_SYMBOL_TRANSFORM is
245 the SymbolTransform to wrap."""
247 assert isinstance(cvs_path, str)
248 self.__subtree = os.path.normcase(os.path.normpath(cvs_path))
249 self.__inner = inner_symbol_transform
251 def __does_rule_apply_to(self, cvs_file):
252 cvs_path = os.path.normcase(os.path.normpath(cvs_file.filename))
253 while cvs_path != self.__subtree:
254 new_cvs_path = os.path.dirname(cvs_path)
255 if new_cvs_path == cvs_path:
256 return False
257 cvs_path = new_cvs_path
258 return True
260 def transform(self, cvs_file, symbol_name, revision):
261 if self.__does_rule_apply_to(cvs_file):
262 return self.__inner.transform(cvs_file, symbol_name, revision)
263 else:
264 # Rule does not apply to that path; return symbol name unaltered.
265 return symbol_name