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

Source Code for Module pybaz._patchlog

  1  # arch-tag: a61e4b6e-1447-4704-a7f3-947a2ffe11de 
  2  # Copyright (C) 2003--2005 David Allouche <david@allouche.net> 
  3  # 
  4  #    This program is free software; you can redistribute it and/or modify 
  5  #    it under the terms of the GNU General Public License as published by 
  6  #    the Free Software Foundation; either version 2 of the License, or 
  7  #    (at your option) any later version. 
  8  # 
  9  #    This program is distributed in the hope that it will be useful, 
 10  #    but WITHOUT ANY WARRANTY; without even the implied warranty of 
 11  #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 12  #    GNU General Public License for more details. 
 13  # 
 14  #    You should have received a copy of the GNU General Public License 
 15  #    along with this program; if not, write to the Free Software 
 16  #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 17   
 18  """Internal module providing patchlog parsing. 
 19   
 20  This module implements some of public interface for the 
 21  pybaz_ package. But for convenience reasons the author prefers 
 22  to store this code in a file separate from ``__init__.py``. 
 23   
 24  .. _pybaz: pybaz-module.html 
 25   
 26  This module is strictly internal and should never be used. 
 27  """ 
 28   
 29  import os 
 30  import email 
 31  import email.Parser 
 32   
 33  import errors 
 34  from pathname import FileName 
 35  from _nameparser import NameParser 
 36  from _escaping import name_unescape 
 37  from deprecation import deprecated_callable 
 38   
 39  __all__ = [ 
 40      'Patchlog', 
 41      ] 
 42   
 43   
44 -def _factory():
45 import pybaz 46 return pybaz.factory
47
48 -def _is_unsafe(obj):
49 import _builtin 50 return isinstance(obj, _builtin._unsafe)
51
52 -def _backend():
53 import pybaz 54 return pybaz.backend
55 56
57 -class Patchlog(object):
58 59 """Log entry associated to a revision. 60 61 May be produced by `Revision.patchlog` or `ArchSourceTree.iter_logs()`. It 62 provides an extensive summary of the associated changeset, a natural 63 language description of the changes, and any number of user-defined 64 extension headers. 65 66 Patchlogs are formatted according to RFC-822, and are parsed using the 67 standard email-handling facilities. 68 69 Note that the patchlog text is not actually parsed before it is needed. 70 That deferred evaluation feature is implemented in the `_parse` method. 71 72 The fundamental accessors are `__getitem__`, which give the text of a named 73 patchlog header, and the `description` property which gives the text of the 74 patchlog body, that is anything after the headers. 75 76 Additional properties provide appropriate standard conversion of the 77 standard headers. 78 """ 79
80 - def __init__(self, revision, tree=None, fromlib=False):
81 """Patchlog associated to the given revision. 82 83 The patchlog may be retrieved from the provided ``tree``, from the 84 revision library if ``fromlib`` is set, or from the archive. 85 86 :param tree: source tree to retrieve the patchlog from. 87 :type tree: `ArchSourceTree`, None 88 :param fromlib: retrieve the patchlog from the revision library. 89 :type fromlib: bool 90 :raise ValueError: tree and fromlib are both set. 91 """ 92 if tree is not None and fromlib: 93 raise ValueError("both tree and fromlib arguments are set.") 94 if _factory().isRevision(revision): 95 self.__revision = revision.fullname 96 elif _is_unsafe(revision): 97 self.__revision, = revision 98 else: 99 p = NameParser(revision) 100 if not p.has_archive() or not p.has_patchlevel: 101 raise errors.NamespaceError(revision, 102 'fully-qualified revision') 103 self.__revision = revision 104 if tree is None: 105 self.__tree = None 106 else: 107 self.__tree = str(tree) 108 self.__fromlib = bool(fromlib) 109 self.__email = None
110
111 - def tree(self): return self.__tree
112 tree = property(tree) 113
114 - def fromlib(self): return self.__fromlib
115 fromlib = property(fromlib) 116
117 - def __repr__(self):
118 fromtree = self.tree is not None 119 fromlib = self.fromlib 120 if not fromtree and not fromlib: 121 return 'Patchlog(%r)' % (self.__revision,) 122 elif fromtree and not fromlib: 123 return 'Patchlog(%r, tree=%r)' % (self.__revision, self.tree) 124 elif not fromtree and fromlib: 125 return 'Patchlog(%r, fromlib=True)' % (self.__revision,) 126 else: 127 raise AssertionError
128
129 - def _parse(self):
130 """Deferred parsing of the log text.""" 131 if self.__email is not None: 132 return self.__email 133 if self.tree is not None: 134 revision = _factory().Revision(self.__revision) 135 log_path = os.path.join( 136 str(self.tree), '{arch}', revision.category.nonarch, 137 revision.branch.nonarch, revision.version.nonarch, 138 revision.archive.name, 'patch-log', revision.patchlevel) 139 s = open(log_path).read() 140 elif self.fromlib: 141 s = _backend().text_cmd(('library-log', self.__revision)) 142 else: 143 s = _backend().text_cmd(('cat-archive-log', self.__revision)) 144 self.__email = email.Parser.Parser().parsestr(s) 145 return self.__email
146
147 - def __getitem__(self, header):
148 """Text of a patchlog header by name. 149 150 :param header: name of a patchlog header. 151 :type header: str 152 :return: text of the header, or None if the header is not present. 153 :rtype: str, None 154 """ 155 return self._parse()[header]
156
157 - def items(self):
158 """List of 2-tuples containing all headers and values. 159 160 :rtype: list of 2-tuple of str 161 """ 162 return self._parse().items()
163
164 - def get_revision(self):
165 """Deprecated. 166 167 Revision associated to this patchlog. 168 169 :rtype: `Revision` 170 :see: `Patchlog.revision` 171 """ 172 deprecated_callable(self.get_revision, (Patchlog, 'revision')) 173 return self.revision
174
175 - def _get_revision(self):
176 assert self.__revision == self['Archive']+'/'+self['Revision'] 177 return _factory().Revision(self.__revision)
178 179 revision = property(_get_revision, doc=""" 180 Revision associated to this patchlog. 181 182 :type: `Revision` 183 """) 184
185 - def get_summary(self):
186 """Deprecated. 187 188 Patchlog summary, a one-line natural language description. 189 190 :rtype: str 191 :see: `Patchlog.summary` 192 """ 193 deprecated_callable(self.get_summary, (Patchlog, 'summary')) 194 return self.summary
195
196 - def _get_summary(self):
197 return self['Summary']
198 199 summary = property(_get_summary, doc=""" 200 Patchlog summary, a one-line natural language description. 201 202 :type: str 203 """) 204
205 - def get_description(self):
206 """Deprecated. 207 208 Patchlog body, a long natural language description. 209 210 :rtype: str 211 :see: `Patchlog.description` 212 """ 213 deprecated_callable(self.get_description, (Patchlog, 'description')) 214 return self.description
215
216 - def _get_description(self):
217 m = self._parse() 218 assert not m.is_multipart() 219 return m.get_payload()
220 221 description = property(_get_description, doc=""" 222 Patchlog body, a long natural language description. 223 224 :type: str 225 """) 226
227 - def get_date(self):
228 """Deprecated. 229 230 For the description of the local time tuple, see the 231 documentation of the `time` module. 232 233 :rtype: local time tuple 234 :see: `Patchlog.date` 235 """ 236 deprecated_callable(self.get_date, (Patchlog, 'date')) 237 return self.date
238
239 - def _get_date(self):
240 d = self['Standard-date'] 241 from time import strptime 242 return strptime(d, '%Y-%m-%d %H:%M:%S %Z')
243 244 date = property(_get_date, doc=""" 245 Time of the associated revision. 246 247 For the description of the local time tuple, see the documentation 248 of the `time` module. 249 250 :type: local time tuple 251 """) 252
253 - def get_creator(self):
254 """Deprecated. 255 256 User id of the the creator of the associated revision. 257 258 :rtype: str 259 :see: `Patchlog.creator` 260 """ 261 deprecated_callable(self.get_creator, (Patchlog, 'creator')) 262 return self.creator
263
264 - def _get_creator(self):
265 return self['Creator']
266 267 creator = property(_get_creator, doc=""" 268 User id of the the creator of the associated revision. 269 270 :type: str 271 """) 272
273 - def get_continuation(self):
274 """Deprecated. 275 276 Ancestor of tag revisions. 277 None for commit and import revisions. 278 279 :rtype: `Revision`, None. 280 :see: `Patchlog.continuation_of` 281 """ 282 deprecated_callable( 283 self.get_continuation, (Patchlog, 'continuation_of')) 284 return self.continuation_of
285
286 - def _get_continuation(self):
287 deprecated_callable( 288 (Patchlog, 'continuation'), (Patchlog, 'continuation_of')) 289 return self.continuation_of
290 291 continuation = property(_get_continuation, doc=""" 292 Deprecated. 293 294 Ancestor of tag revisions. 295 None for commit and import revisions. 296 297 :type: `Revision`, None. 298 :see: `Patchlog.continuation_of` 299 """) 300
301 - def _get_continuation_of(self):
302 c = self['Continuation-of'] 303 if c is None: 304 return None 305 return _factory().Revision(c)
306 307 continuation_of = property(_get_continuation_of, doc=""" 308 Ancestor of tag revisions. 309 None for commit and import revisions. 310 311 :type: `Revision`, None. 312 """) 313
314 - def get_new_patches(self):
315 """Deprecated. 316 317 New-patches header as an iterable of Revision. 318 319 :rtype: iterable of `Revision` 320 :see: `Patchlog.new_patches` 321 """ 322 deprecated_callable( 323 Patchlog.get_new_patches, (Patchlog, 'new_patches')) 324 return self.new_patches
325
326 - def _get_new_patches(self):
327 patches = self['New-patches'].split() 328 if '!!!!!nothing-should-depend-on-this' in patches: 329 patches.remove('!!!!!nothing-should-depend-on-this') 330 patches.append("%s" % self.revision) 331 return map(_factory().Revision, patches)
332 333 new_patches = property(_get_new_patches, doc=""" 334 New-patches header as an iterable of Revision. 335 336 Patchlogs added in this revision. 337 338 :type: iterable of `Revision` 339 """) 340
341 - def get_merged_patches(self):
342 """Deprecated. 343 344 Revisions merged in this revision. That is the revisions 345 listed in the New-patches header except the revision of the 346 patchlog. 347 348 :rtype: iterable of `Revision` 349 :see: `Patchlog.merged_patches` 350 """ 351 deprecated_callable( 352 Patchlog.get_merged_patches, (Patchlog, 'merged_patches')) 353 return self.merged_patches
354
355 - def _get_merged_patches(self):
356 rvsn = self.revision.fullname 357 return filter(lambda(r): r.fullname != rvsn, self.new_patches)
358 359 merged_patches = property(_get_merged_patches, doc=""" 360 Revisions merged in this revision. 361 362 That is the revisions listed in the New-patches header except the 363 revision of the patchlog. 364 365 :type: iterable of `Revision` 366 """) 367
368 - def get_new_files(self):
369 """Deprecated. 370 371 Source files added in the associated revision. 372 373 :rtype: iterable of `FileName` 374 :see: `Patchlog.new_files` 375 """ 376 deprecated_callable(self.get_new_files, (Patchlog, 'new_files')) 377 return self.new_files
378
379 - def _get_new_files(self):
380 s = self['New-files'] 381 if s is None: return [] 382 return [ FileName(name_unescape(f)) for f in s.split() ]
383 384 new_files = property(_get_new_files, doc=""" 385 Source files added in the associated revision. 386 387 :type: iterable of `FileName` 388 """) 389
390 - def get_modified_files(self):
391 """Deprecated. 392 393 Names of source files modified in the associated revision. 394 395 :rtype: iterable of `FileName` 396 """ 397 deprecated_callable( 398 self.get_modified_files, (Patchlog, 'modified_files')) 399 return self.modified_files
400
401 - def _get_modified_files(self):
402 s = self['Modified-files'] 403 if s is None: return [] 404 return [ FileName(name_unescape(f)) for f in s.split() ]
405 406 modified_files = property(_get_modified_files, doc=""" 407 Names of source files modified in the associated revision. 408 409 :type: iterable of `FileName` 410 """) 411
412 - def get_renamed_files(self):
413 """Deprecated. 414 415 Source files renames in the associated revision. 416 417 Dictionnary whose keys are old names and whose values are the 418 corresponding new names. Explicit file ids are listed in 419 addition to their associated source file. 420 421 :rtype: dict 422 """ 423 deprecated_callable( 424 self.get_renamed_files, (Patchlog, 'renamed_files')) 425 return self.renamed_files
426
427 - def _get_renamed_files(self):
428 renamed_files = self['Renamed-files'] 429 renames = {} 430 if renamed_files is None: 431 return renames 432 names = [name_unescape(name) for name in renamed_files.split()] 433 for i in range(len(names)/2): 434 renames[names[i*2]] = names[i*2+1] 435 return renames
436 437 renamed_files = property(_get_renamed_files, doc=""" 438 Source files renames in the associated revision. 439 440 Dictionnary whose keys are old names and whose values are the 441 corresponding new names. Explicit file ids are listed in 442 addition to their associated source file. 443 444 :type: dict 445 """) 446
447 - def get_removed_files(self):
448 """Deprecated. 449 450 Names of source files removed in the associated revision. 451 452 :rtype: iterable of `FileName` 453 """ 454 deprecated_callable( 455 self.get_removed_files, (Patchlog, 'removed_files')) 456 return self.removed_files
457
458 - def _get_removed_files(self):
459 s = self['Removed-files'] 460 if s is None: return [] 461 return [ FileName(name_unescape(f)) for f in s.split() ]
462 463 removed_files = property(_get_removed_files, doc=""" 464 Names of source files removed in the associated revision. 465 466 :type: iterable of `FileName` 467 """)
468