* cvs2svn_lib/symbol_transform.py: Add an IgnoreSymbolTransform class.
[cvs2svn.git] / cvs2svn_lib / symbol_transform.py
blob968f034ab28881181f21100938c9db4b600e08fe
1 # (Be in -*- python -*- mode.)
3 # ====================================================================
4 # Copyright (c) 2006-2007 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 key = (cvs_filename, symbol_name, revision)
142 if key in self._map:
143 Log().warn(
144 'Overwriting symbol transform for\n'
145 ' filename=%r symbol=%s revision=%s'
146 % (cvs_filename, symbol_name, revision,)
148 self._map[key] = new_name
150 def transform(self, cvs_file, symbol_name, revision):
151 return self._map.get(
152 (cvs_file.filename, symbol_name, revision), symbol_name
156 class SubtreeSymbolMapper(SymbolTransform):
157 """A SymbolTransform that transforms symbols within a whole repo subtree.
159 The user has to specify a CVS repository path (a filename or
160 directory) and the original symbol name. All symbols under that
161 path will be renamed to the specified new name (which can be None if
162 the symbol should be ignored). The mappings can be set via a
163 constructor argument or by calling __setitem__(). Only the most
164 specific rule is applied."""
166 def __init__(self, items=[]):
167 """Initialize the mapper.
169 ITEMS is a list of tuples (cvs_path, symbol_name, new_name)
170 which will be set as mappings. cvs_path is a string naming a
171 directory within the CVS repository."""
173 # A map {symbol_name : {cvs_path : new_name}}:
174 self._map = {}
176 for (cvs_path, symbol_name, new_name) in items:
177 self[cvs_path, symbol_name] = new_name
179 def __setitem__(self, (cvs_path, symbol_name), new_name):
180 """Set a mapping for a particular file and symbol."""
182 try:
183 symbol_map = self._map[symbol_name]
184 except KeyError:
185 symbol_map = {}
186 self._map[symbol_name] = symbol_map
188 if cvs_path in symbol_map:
189 Log().warn(
190 'Overwriting symbol transform for\n'
191 ' directory=%r symbol=%s'
192 % (cvs_path, symbol_name,)
194 symbol_map[cvs_path] = new_name
196 def transform(self, cvs_file, symbol_name, revision):
197 try:
198 symbol_map = self._map[symbol_name]
199 except KeyError:
200 # No rules for that symbol name
201 return symbol_name
203 cvs_path = cvs_file.filename
204 while True:
205 try:
206 return symbol_map[cvs_path]
207 except KeyError:
208 new_cvs_path = os.path.dirname(cvs_path)
209 if new_cvs_path == cvs_path:
210 # No rules found for that path; return symbol name unaltered.
211 return symbol_name
212 else:
213 cvs_path = new_cvs_path
216 class IgnoreSymbolTransform(SymbolTransform):
217 """Ignore symbols matching a specified regular expression."""
219 def __init__(self, pattern):
220 """Create an SymbolTransform that ignores symbols matching PATTERN.
222 PATTERN is a regular expression that should match the whole symbol
223 name."""
225 self.pattern = re.compile('^' + pattern + '$')
227 def transform(self, cvs_file, symbol_name, revision):
228 if self.pattern.match(symbol_name):
229 return None
230 else:
231 return symbol_name