Package pybaz :: Module _output
[frames] | no frames]

Source Code for Module pybaz._output

  1  # arch-tag: 4ccb7253-910d-41a0-8653-d1197af1601a 
  2  # Copyright (C) 2004 David Allouche <david@allouche.net> 
  3  #               2005 Canonical Limited. 
  4  # 
  5  #    This program is free software; you can redistribute it and/or modify 
  6  #    it under the terms of the GNU General Public License as published by 
  7  #    the Free Software Foundation; either version 2 of the License, or 
  8  #    (at your option) any later version. 
  9  # 
 10  #    This program is distributed in the hope that it will be useful, 
 11  #    but WITHOUT ANY WARRANTY; without even the implied warranty of 
 12  #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 13  #    GNU General Public License for more details. 
 14  # 
 15  #    You should have received a copy of the GNU General Public License 
 16  #    along with this program; if not, write to the Free Software 
 17  #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 18   
 19  """ 
 20  Internal module providing output parsing functionality. 
 21   
 22  This module implements some of public interface for the 
 23  pybaz_ package. But for convenience reasons the author prefers 
 24  to store this code in a file separate from ``__init__.py``. 
 25   
 26  .. _pybaz: arch-module.html 
 27   
 28  This module is strictly internal and should never be used. 
 29  """ 
 30   
 31  __all__ = [] 
 32   
 33  from pathname import * 
 34  from _escaping import * 
 35   
 36   
 37  __all__.extend(( 
 38      'Chatter', 
 39      'classify_chatter', 
 40      )) 
 41   
42 -class Chatter(object):
43 """Chatter lines in ``tla`` output. 44 45 :ivar text: chatter text without the ``"* "`` prefix. 46 :type text: str 47 """ 48
49 - def __init__(self, text):
50 """Create a chatter item. 51 52 :param text: tla-style chatter line, starting with ``"* "`` 53 :type text: str 54 """ 55 assert text[:2] == '* ' 56 self.text = text[2:]
57
58 - def __str__(self):
59 """tla-style chatter line with ``"* "`` prefix.""" 60 return "* " + self.text
61 62
63 -def classify_chatter(iter):
64 """Classify chatter in a sequence of strings. 65 66 Generator that yields Chatter objects for chatter lines, and 67 yields other lines verbatim. 68 69 :param iter: iterable of str 70 :rtype: iterable of `Chatter` or str 71 """ 72 for line in iter: 73 if line.startswith("* "): 74 yield Chatter(line) 75 else: 76 yield line
77 78 79 __all__.extend(( 80 'MergeOutcome', 81 'TreeChange', 82 )) 83
84 -class MergeOutcome(object):
85 """Abstract base class for changeset application summary output lines. 86 87 :ivar name: name of the corresponding file 88 :type name: `FileName` or `DirName` 89 :ivar directory: does the change applies to a directory? 90 :type directory: bool 91 """ 92
93 - def _parse(self, text, pad, filepre, dirpre=None):
94 self._pad = pad 95 assert 2 == len(filepre) 96 assert dirpre is None or 2 == len(dirpre) 97 prefix = text[0:2+len(pad)] 98 if prefix == filepre+pad: 99 self._directory = False 100 elif dirpre is not None and prefix == dirpre+pad: 101 self._directory = True 102 else: 103 raise AssertionError('bad prefix: %r' % prefix) 104 name = name_unescape(text[len(prefix):]) 105 self._name = (FileName, DirName)[self._directory](name)
106
107 - def __str__(self):
108 """tla-style changeset application line.""" 109 raise NotImplementedError
110
111 - def _to_str(self, filepre, dirpre=None):
112 if self._directory: 113 return ''.join((dirpre, self._pad, name_escape(self.name))) 114 else: 115 return ''.join((filepre, self._pad, name_escape(self.name)))
116
117 - def _get_directory(self):
118 return self._directory
119 directory = property(_get_directory) 120
121 - def _get_name(self):
122 return self._name
123 name = property(_get_name)
124 125
126 -class TreeChange(MergeOutcome):
127 """Abstract base class for ``changes`` summary output lines.""" 128
129 - def __str__(self):
130 """tla-style change line,""" 131 raise NotImplementedError
132 133 134 __all__.extend(( 135 'FileAddition', 136 'FileDeletion', 137 'FileModification', 138 'FilePermissionsChange', 139 'FileRename', 140 'SymlinkModification', 141 )) 142
143 -class FileAddition(TreeChange):
144 """Changeset summary line for a new file. 145 146 :ivar name: name of the added file or directory. 147 :ivar directory: is ``name`` a directory name? 148 """ 149
150 - def __init__(self, text, pad):
151 self._parse(text, pad, 'A ', 'A/')
152
153 - def __str__(self):
154 return self._to_str('A ', 'A/')
155 156
157 -class FileDeletion(TreeChange):
158 """Changeset summary line for a deleted file. 159 160 :ivar name: name of the deleted file or directory. 161 :ivar directory: is ``name`` a directory name? 162 """ 163
164 - def __init__(self, text, pad):
165 self._parse(text, pad, 'D ', 'D/')
166
167 - def __str__(self):
168 return self._to_str('D ', 'D/')
169 170
171 -class FileModification(TreeChange):
172 """Changeset summary line for file whose contents were modified. 173 174 :ivar name: name of the modified file. 175 :ivar binary: is ``name`` a binary file? 176 :type binary: bool 177 :ivar directory: always False 178 """ 179
180 - def __init__(self, text, pad):
181 self._directory = False 182 self._pad = pad 183 prefix = text[0:2+len(pad)] 184 if prefix == 'M '+pad: self.binary = False 185 elif prefix == 'Mb'+pad: self.binary = True 186 else: raise AssertionError('bad prefix: %r' % prefix) 187 name = name_unescape(text[len(prefix):]) 188 self._name = FileName(name)
189
190 - def __str__(self):
191 if self.binary: 192 return 'Mb%s%s' % (self._pad, name_escape(self.name)) 193 else: 194 return 'M %s%s' % (self._pad, name_escape(self.name))
195 196
197 -class FilePermissionsChange(TreeChange):
198 """Changeset summary line for a change in permissions. 199 200 :ivar name: name of the file or directory whose permissions changed. 201 :ivar directory: is ``name`` a directory name? 202 """ 203
204 - def __init__(self, text, pad):
205 if text.startswith('--/ '): 206 self._patching = True 207 self._directory = True 208 name = name_unescape(text[len('--/ '):]) 209 self._name = DirName(name) 210 else: 211 self._patching = False 212 self._parse(text, pad, '--', '-/')
213
214 - def __str__(self):
215 if self._patching: 216 assert self._directory 217 return '--/ ' + name_escape(self.name) 218 else: 219 return self._to_str('--', '-/')
220 221
222 -class FileRename(TreeChange):
223 """Changeset summary line for a renaming. 224 225 :ivar name: new name of the moved file or directory. 226 :ivar oldname: old name of the moved file or directory. 227 :type oldname: `FileName`, `DirName` 228 :ivar directory: are ``name`` and ``oldname`` directory names? 229 """ 230
231 - def __init__(self, text, pad):
232 self._pad = pad 233 prefix = text[0:2+len(pad)] 234 if prefix == '=>'+pad: 235 self._directory = False 236 elif prefix == '/>'+pad: 237 self._directory = True 238 else: 239 raise AssertionError('bad prefix: %r' % prefix) 240 oldnew = text[len(prefix):].split('\t') 241 assert len(oldnew) == 2 242 ctor = (FileName, DirName)[self.directory] 243 self.oldname, self._name = map(ctor, map(name_unescape, oldnew))
244
245 - def __str__(self):
246 if self.directory: 247 format = '/>%s%s\t%s' 248 else: 249 format = '=>%s%s\t%s' 250 names = map(name_escape, (self.oldname, self.name)) 251 return format % ((self._pad,) + tuple(names))
252 253
254 -class SymlinkModification(TreeChange):
255 """Changeset summary line for a symlink modification. 256 257 :ivar name: name of the modified symbolic link. 258 :ivar directory: always False 259 """ 260
261 - def __init__(self, text, pad):
262 self._parse(text, pad, '->')
263
264 - def __str__(self):
265 return self._to_str('->')
266 267 268 __all__.extend(( 269 'classify_changeset_creation', 270 'PatchConflict', 271 'classify_changeset_application', 272 )) 273
274 -def classify_changeset_creation(lines, pad=' '):
275 """Classify the output of a changeset creation command. 276 277 :param lines: incremental output from a changeset creation command. 278 :type lines: iterator of str 279 :param pad: padding text between prefix and file name. 280 :type pad: str 281 :rtype: Iterable of `Chatter`, `TreeChange`, str 282 283 :note: diff output (*e.g.* ``changes --diffs``) is not supported. 284 """ 285 for line in classify_chatter(lines): 286 if isinstance(line, Chatter) or len(line) < 2: 287 yield line 288 elif line[:3] in ('A ', 'A/ '): 289 yield FileAddition(line, pad) 290 elif line[:3] in ('D ', 'D/ '): 291 yield FileDeletion(line, pad) 292 elif line[:3] in ('M ', 'Mb '): 293 yield FileModification(line, pad) 294 elif line[:3] in ('-- ', '-/ '): 295 yield FilePermissionsChange(line, pad) 296 elif line[:3] in ('=> ', '/> '): 297 yield FileRename(line, pad) 298 elif line[:3] == '-> ': 299 yield SymlinkModification(line, pad) 300 else: 301 yield line
302 303
304 -class PatchConflict(MergeOutcome):
305 """Changeset application summary line for a patch conflict. 306 307 :ivar name: name of the file where the patch conflict occurred. 308 :ivar directory: always False 309 """
310 - def __init__(self, text):
311 self._parse(text, ' ', 'C ')
312
313 - def __str__(self):
314 return self._to_str('C ')
315 316
317 -def classify_changeset_application(lines):
318 """Classify the output of a change-producing command. 319 320 :param lines: incremental output from a changeset application command. 321 :type lines: iterable of str 322 :rtype: iterable of `Chatter`, `MergeOutcome`, str 323 324 :note: diff output (*e.g.* ``changes --diffs``) is not supported. 325 """ 326 for line in classify_changeset_creation(lines, pad=' '): 327 if isinstance(line, (Chatter, TreeChange)) or len(line) < 2: 328 yield line 329 elif line[:4] == '--/ ': 330 yield FilePermissionsChange(line, pad=' ') 331 elif line[:4] == 'C ': 332 yield PatchConflict(line) 333 else: 334 yield line
335