Package svnmailer :: Module struct_accessors
[hide private]

Source Code for Module svnmailer.struct_accessors

  1  # -*- coding: utf-8 -*- 
  2  # pylint: disable-msg = W0232, W0613 
  3  # 
  4  # Copyright 2004-2006 André Malo or his licensors, as applicable 
  5  # 
  6  # Licensed under the Apache License, Version 2.0 (the "License"); 
  7  # you may not use this file except in compliance with the License. 
  8  # You may obtain a copy of the License at 
  9  # 
 10  #     http://www.apache.org/licenses/LICENSE-2.0 
 11  # 
 12  # Unless required by applicable law or agreed to in writing, software 
 13  # distributed under the License is distributed on an "AS IS" BASIS, 
 14  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 15  # See the License for the specific language governing permissions and 
 16  # limitations under the License. 
 17  """ 
 18  Struct Member Definitions 
 19  ========================= 
 20   
 21  This module exactly describes the struct member definitions used by 
 22  the svnmailer. All those definitions are pooled in the L{typemap} dict, which 
 23  can be supplied as-is to L{typedstruct.members}. 
 24   
 25  The following types are defined by this module: 
 26      - C{unicode}: see L{UnicodeDescriptor} 
 27      - C{string}: see L{StringDescriptor} 
 28      - C{int}: see L{IntegerDescriptor} 
 29      - C{bool}: see L{BooleanDescriptor} 
 30      - C{humanbool}: see L{HumanBooleanDescriptor} 
 31      - C{regex}: see L{RegexDescriptor} 
 32      - C{token}: see L{TokenDescriptor} 
 33      - C{tokenlist}: see L{TokenlistDescriptor} 
 34      - C{filename}: see L{FilenameDescriptor} 
 35      - C{unicommand}: see L{CommandlineDescriptor} 
 36      - C{quotedstr} : see L{QuotedstringDescriptor} 
 37      - C{stdin}: see L{StdinDescriptor} 
 38      - C{mailaction}: see L{MailactionDescriptor} 
 39   
 40  @var typemap: The type mapping dict (C{{<name>: <class>}}) 
 41  @type typemap: C{svnmailer.util.ReadOnlyDict} 
 42  """ 
 43  __author__    = "André Malo" 
 44  __docformat__ = "epytext en" 
 45  __all__       = ['typemap'] 
 46   
 47  # global imports 
 48  from svnmailer import typedstruct, util 
 49   
 50   
 51  # Exceptions 
52 -class NotSupportedError(NotImplementedError):
53 """ This method is not supported """ 54 pass
55 56
57 -class BaseDescriptor(typedstruct.MemberDescriptor):
58 """ Base class for svnmailer descriptors """ 59
60 - def __init__(self, name, private, param = None):
61 """ Initialization """ 62 super(BaseDescriptor, self).__init__(name, private, param or {}) 63 self.MAP = bool(self.param.get('map')) 64 self.SUBST = bool(self.param.get('subst'))
65 66
67 - def transform(self, value, arg):
68 """ Transform if value is not None """ 69 if value is not None: 70 value = self.doTransform(value, arg) 71 72 return value
73 74
75 - def substitute(self, value, subst, arg):
76 """ Substitute the value if it's activated """ 77 if self.SUBST and value is not None: 78 value = self.doSubstitute(value, subst, arg) 79 80 return value
81 82
83 - def premap(self, value, mapper, arg):
84 """ Premap the value if it's activated """ 85 if self.MAP and value is not None: 86 value = self.doPremap(value, mapper, arg) 87 88 return value
89 90
91 - def postmap(self, value, mapper, arg):
92 """ Postmap the value if it's activated """ 93 if self.MAP and value is not None: 94 value = self.doPostmap(value, mapper, arg) 95 96 return value
97 98
99 - def getCharset(self, arg):
100 """ Returns the charset """ 101 return (arg and arg["encoding"]) or 'us-ascii'
102 103
104 - def getFileCharset(self, arg):
105 """ Returns the file system charset """ 106 return arg and arg["path_encoding"]
107 108
109 - def doPremap(self, value, mapper, arg):
110 """ abstract method 111 112 @param value: The value to premap 113 @type value: any 114 115 @param mapper: The mapper function 116 @type mapper: C{function} 117 118 @param arg: The argument used for struct initialization 119 @type arg: any 120 """ 121 raise NotSupportedError()
122 123
124 - def doTransform(self, value, arg):
125 """ abstract method 126 127 @param value: The value to tranform 128 @type value: any 129 130 @param arg: The argument used for struct initialization 131 @type arg: any 132 133 @return: The transformed value 134 @rtype: any 135 """ 136 raise NotSupportedError()
137 138
139 - def doSubstitute(self, value, subst, arg):
140 """ abstract method 141 142 @param value: The value to substitute 143 @type value: any 144 145 @param subst: The substitution dictionary 146 @type subst: C{dict} 147 148 @param arg: The argument used for struct initialization 149 @type arg: any 150 151 @return: The substituted value 152 @rtype: any 153 """ 154 raise NotSupportedError()
155 156
157 - def doPostmap(self, value, mapper, arg):
158 """ abstract method 159 160 @param value: The value to premap 161 @type value: any 162 163 @param mapper: The mapper function 164 @type mapper: C{function} 165 166 @param arg: The argument used for struct initialization 167 @type arg: any 168 """ 169 raise NotSupportedError()
170 171
172 -class BasePremapDescriptor(BaseDescriptor):
173 """ Base class for premap only descriptors """ 174
175 - def doPremap(self, value, mapper, arg):
176 """ Maps the value """ 177 return mapper(value)
178 179
180 - def doPostmap(self, value, mapper, arg):
181 """ Passes through """ 182 return value
183 184
185 -class BasePostmapDescriptor(BaseDescriptor):
186 """ Base class for postmap only descriptors """ 187
188 - def doPremap(self, value, mapper, arg):
189 """ Passes through """ 190 return value
191 192
193 -class UnicodeDescriptor(BasePostmapDescriptor):
194 """ Unicode object storage """ 195
196 - def doTransform(self, value, arg):
197 """ Transforms the value to unicode if it wasn't already 198 199 @exception TypeError: The supplied value is neither C{str} nor 200 C{unicode} 201 @exception UnicodeError: The supplied value is a string and cannot 202 be interpreted as the specified charset 203 """ 204 if isinstance(value, str): 205 value = value.decode(self.getCharset(arg)) 206 elif not isinstance(value, unicode): 207 raise TypeError( 208 "Supplied value must be string or unicode, not %r" % 209 type(value).__name__ 210 ) 211 212 return value
213 214
215 - def doSubstitute(self, value, subst, arg):
216 """ Substitutes the value """ 217 return util.substitute(value, subst)
218 219
220 - def doPostmap(self, value, mapper, arg):
221 """ Maps the value 222 223 @exception TypeError: The mapped value is neither C{str} nor 224 C{unicode} 225 @exception UnicodeError: The mapped value is a string and cannot 226 be interpreted as the specified charset 227 """ 228 return self.transform(mapper(value), arg)
229 230
231 -class StringDescriptor(BaseDescriptor):
232 """ String storage """ 233
234 - def doTransform(self, value, arg):
235 """ Turns into a string 236 237 @exception TypeError: The supplied value is not convertable 238 @exception ValueError: The supplied value is not convertable 239 """ 240 return str(value)
241 242
243 -class IntegerDescriptor(BaseDescriptor):
244 """ Integer storage """ 245
246 - def doTransform(self, value, arg):
247 """ Turns into an int 248 249 @exception TypeError: The supplied value is not convertable 250 @exception ValueError: The supplied value is not convertable 251 """ 252 return int(value)
253 254
255 -class BooleanDescriptor(BaseDescriptor):
256 """ Boolean storage """ 257
258 - def doTransform(self, value, arg):
259 """ Turns into a boolean """ 260 return bool(value)
261 262
263 -class HumanBooleanDescriptor(BaseDescriptor):
264 """ Boolean storage with translater from human readable booleans 265 266 @cvar TRUE: The true words (C{tuple} of C{str}) 267 @type TRUE: C{tuple} 268 269 @cvar FALSE: The false words (C{tuple} of C{str}) 270 @type FALSE: C{tuple} 271 272 @ivar HUMAN: The dictionary containing true and false keys 273 @type HUMAN: C{dict} 274 """ 275 TRUE = ('1', 'yes', 'on', 'true') 276 FALSE = ('', '0', 'no', 'off', 'false', 'none') 277
278 - def __init__(self, name, private, param = None):
279 """ Initialization """ 280 super(HumanBooleanDescriptor, self).__init__(name, private, param) 281 282 self.HUMAN = dict.fromkeys(self.TRUE, True) 283 self.HUMAN.update(dict.fromkeys(self.FALSE, False))
284 285
286 - def doTransform(self, value, arg):
287 """ Turns into boolean 288 289 @exception ValueError: The supplied value was not recognized as 290 human boolean 291 """ 292 try: 293 return self.HUMAN[str(value).lower()] 294 except KeyError: 295 raise ValueError( 296 "Value %r means neither 'true' nor 'false'" % value 297 )
298 299
300 -class RegexDescriptor(BasePremapDescriptor):
301 """ Regex storage 302 303 @ivar FLAGS: The flags for the regex compiler 304 @type FLAGS: C{int} 305 """ 306
307 - def __init__(self, name, private, param = None):
308 """ Initialization """ 309 super(RegexDescriptor, self).__init__(name, private, param) 310 self.FLAGS = self.param.get('flags', 0)
311 312
313 - def doTransform(self, value, arg):
314 """ Turns into a regex 315 316 @exception TypeError: Invalid type of value or the flags 317 are broken 318 @exception ValueError: The regex could not be compiled 319 @exception UnicodeError: The supplied value was a string and 320 could not be converted to unicode 321 """ 322 import re 323 324 if isinstance(value, str): 325 value = value.decode(self.getCharset(arg)) 326 327 try: 328 value = re.compile(value, self.FLAGS) 329 except re.error: 330 raise ValueError("Regex %r could not be compiled" % value) 331 332 return value
333 334
335 -class TokenDescriptor(BasePremapDescriptor):
336 """ Unicode token storage """ 337
338 - def __init__(self, name, private, param = None):
339 """ Initialization """ 340 super(TokenDescriptor, self).__init__(name, private, param) 341 342 allowed = self.param.get('allowed') 343 if allowed: 344 self.ALLOWED = dict.fromkeys([token.lower() for token in allowed]) 345 else: 346 self.ALLOWED = None
347 348
349 - def doTransform(self, value, arg):
350 """ Transforms the value to unicode if it wasn't already 351 352 @exception TypeError: The supplied value is neither C{str} nor 353 C{unicode} 354 @exception UnicodeError: The supplied value is a string and cannot 355 be interpreted as the specified charset 356 @exception ValueError: The supplied value is not allowed 357 """ 358 if isinstance(value, str): 359 value = value.decode(self.getCharset(arg)) 360 elif not isinstance(value, unicode): 361 raise TypeError( 362 "Supplied value must be string or unicode, not %r" % 363 type(value).__name__ 364 ) 365 366 value = value.lower() 367 if self.ALLOWED is not None and value and \ 368 not self.ALLOWED.has_key(value): 369 raise ValueError( 370 "Supplied token %r is not allowed" % value 371 ) 372 373 return value
374 375
376 -class TokenlistDescriptor(BasePostmapDescriptor):
377 """ (Unicode) Tokenlist storage """ 378
379 - def __init__(self, name, private, param = None):
380 """ Initialization """ 381 super(TokenlistDescriptor, self).__init__(name, private, param) 382 383 allowed = self.param.get('allowed') 384 if allowed: 385 self.ALLOWED = dict.fromkeys([token.lower() for token in allowed]) 386 else: 387 self.ALLOWED = None
388 389
390 - def doTransform(self, value, arg):
391 """ Turns into a token list 392 393 @exception UnicodeError: The supplied value was a string and could 394 not be converted to unicode 395 @exception TypeError: The input value is neither string nor unicode 396 nor a tuple 397 @exception ValueError: At least one of the tokens is not allowed 398 """ 399 if isinstance(value, tuple): 400 pass 401 else: 402 if isinstance(value, str): 403 value = value.decode(self.getCharset(arg)) 404 elif not isinstance(value, unicode): 405 raise TypeError( 406 "Supplied value must be string or unicode, not %r" % 407 type(value).__name__ 408 ) 409 410 value = tuple(value.split()) 411 412 if not self.MAP and self.ALLOWED is not None: 413 for token in value: 414 if not self.ALLOWED.has_key(token.lower()): 415 raise ValueError( 416 "Supplied token %r is not allowed" % token 417 ) 418 419 return value
420 421
422 - def doSubstitute(self, value, subst, arg):
423 """ Substitutes the items """ 424 return tuple([util.substitute(token, subst) for token in value])
425 426
427 - def doPostmap(self, value, mapper, arg):
428 """ Maps the items """ 429 value = tuple([mapper(token) for token in value]) 430 431 if self.ALLOWED: 432 for token in value: 433 if not self.ALLOWED.has_key(token.lower()): 434 raise ValueError( 435 "Supplied token %r is not allowed" % token 436 ) 437 438 return value
439 440
441 -class FilenameDescriptor(BasePremapDescriptor):
442 """ Filename storage """ 443
444 - def doTransform(self, value, arg):
445 """ Stores a file name either as string or unicode (depending on OS) 446 447 @exception TypeError: The supplied value is neither C{str} nor 448 C{unicode} 449 @exception UnicodeError: The supplied value cannot be recoded 450 """ 451 if hasattr(value, '_already_recoded_filename'): 452 pass 453 elif isinstance(value, str) or isinstance(value, unicode): 454 value = util.filename.toLocale( 455 value, self.getCharset(arg), self.getFileCharset(arg) 456 ) 457 class RecodedFilename(type(value)): 458 """ Designates the recoded file name """ 459 _already_recoded_filename = True
460 value = RecodedFilename(value) 461 else: 462 raise TypeError( 463 "Supplied value must be string or unicode, not %r" % 464 type(value).__name__ 465 ) 466 467 return value
468 469
470 -class CommandlineDescriptor(BasePremapDescriptor):
471 """ Commandline storage """ 472
473 - def doTransform(self, value, arg):
474 """ Parses as command line with quoted arguments 475 476 @exception UnicodeError: C{value} could not be en-/decoded 477 @exception TypeError: C{value} is neither string nor unicode nor 478 tuple 479 @exception ValueError: C{value} represents an invalid command 480 line 481 """ 482 if value == '': 483 value = None 484 elif isinstance(value, tuple): 485 pass 486 elif type(value) in (str, unicode): 487 value = util.splitCommand(value) 488 coding = self.getCharset(arg) 489 if isinstance(value[0], str): 490 value[1:] = [item.decode(coding) for item in value[1:]] 491 value[0] = util.filename.toLocale( 492 value[0], coding, self.getFileCharset(arg) 493 ) 494 value = tuple(value) 495 else: 496 raise TypeError( 497 "Supplied value must be string or unicode, not %r" % 498 type(value).__name__ 499 ) 500 501 return value
502 503
504 -class _QuotedString(str):
505 """ Holds a quoted string """ 506 import re 507 _parsed_quoted_string = ( 508 re.compile(r'(?:[^"\s]\S*|"[^\\"]*(?:\\[\\"][^\\"]*)*")$'), 509 re.compile(r'\\([\\"])') 510 ) 511 del re 512
513 - def __new__(cls, value = ''):
514 """ Initialization and check 515 516 @param value: The value to initialize the string 517 @type value: C{str} 518 519 @exception ValueError: The string did not pass the test 520 """ 521 checkre, subre = cls._parsed_quoted_string 522 if value and not checkre.match(value): 523 raise ValueError("Could not parse quoted string %r" % value) 524 525 if value.startswith('"'): 526 value = subre.sub(r'\1', value[1:-1]) 527 528 return str.__new__(cls, value)
529 530
531 - def __repr__(self):
532 """ Returns the representation of the quoted string """ 533 return repr( 534 '"%s"' % str(self).replace('\\', r'\\').replace('"', r'\"') 535 )
536 537
538 -class QuotedstringDescriptor(BasePremapDescriptor):
539 """ Quoted string storage """ 540
541 - def doTransform(self, value, arg):
542 """ Parses value as quoted string 543 544 @exception ValueError: The value is not parsable as quoted string 545 """ 546 if hasattr(value, '_parsed_quoted_string'): 547 pass 548 elif isinstance(value, str): 549 value = _QuotedString(value) 550 else: 551 raise TypeError( 552 "Supplied value must be a string, not %r" % 553 type(value).__name__ 554 ) 555 556 return value
557 558
559 -class StdinDescriptor(BaseDescriptor):
560 """ Stdin storage """ 561 _stdin = None 562
563 - def substitute(self, value, subst, arg):
564 """ Read stdin once and return it as string """ 565 if StdinDescriptor._stdin is None: 566 import sys 567 StdinDescriptor._stdin = sys.stdin.read() 568 569 return StdinDescriptor._stdin
570 571
572 -class _MailAction(object):
573 """ Mailaction container 574 575 @cvar TRUNCATE: C{truncate} token 576 @type TRUNCATE: C{str} 577 578 @cvar URLS: C{showurls} token 579 @type URLS: C{str} 580 581 @cvar SPLIT: C{split} token 582 @type SPLIT: C{str} 583 584 @cvar REVPROP: C{revprop-changes} token 585 @type REVPROP: C{str} 586 587 @cvar LOCKS: C{locks} token 588 @type LOCKS: C{str} 589 590 @ivar maxbytes: maximum number of bytes 591 @type maxbytes: C{int} 592 593 @ivar mode: basic mode (C{truncate}, C{showurls}, C{split}) 594 @type mode: C{str} 595 596 @ivar truncate: truncate submode 597 @type truncate: C{bool} 598 599 @ivar drop: drop submode or C{None} 600 @type drop: C{int} 601 602 @ivar scope: additional scopes (C{revprop-changes}, C{locks}) 603 @type scope: C{tuple} 604 """ 605 maxbytes = 0 606 mode = None 607 truncate = False 608 drop = None 609 scope = () 610 611 TRUNCATE = "truncate" 612 URLS = "showurls" 613 SPLIT = "split" 614 615 REVPROP = "revprop-changes" 616 LOCKS = "locks" 617
618 - def __init__(self, action):
619 """ Initialization 620 621 @param action: The action as string 622 @type action: C{str} 623 """ 624 _msg = "Can't parse action string %r" % (action,) 625 626 action = action.split() 627 if len(action) < 2: 628 raise ValueError(_msg) 629 630 self.maxbytes = int(action[0]) 631 tokens = action[1].lower().split('/') 632 633 self.mode = tokens.pop(0).lower() 634 if self.mode not in (self.TRUNCATE, self.URLS, self.SPLIT): 635 raise ValueError(_msg) 636 637 if tokens and tokens[0].lower() == self.TRUNCATE: 638 if self.mode not in (self.URLS, self.SPLIT): 639 raise ValueError(_msg) 640 self.truncate = True 641 tokens.pop(0) 642 643 if tokens: 644 if self.mode != self.SPLIT: 645 raise ValueError(_msg) 646 self.drop = int(tokens.pop(0)) 647 648 if tokens: 649 raise ValueError(_msg) 650 651 sdict = {} 652 for token in action[2:]: 653 if token.lower() not in (self.REVPROP, self.LOCKS): 654 raise ValueError(_msg) 655 sdict[token.lower()] = None 656 self.scope = tuple(sdict.keys())
657 658
659 - def __repr__(self):
660 """ String representation of the object 661 662 @return: The representation 663 @rtype: C{str} 664 """ 665 result = "%d %s" % (self.maxbytes, self.mode) 666 if self.truncate: 667 result = "%s/truncate" % result 668 if self.drop: 669 result = "%s/%d" % (result, self.drop) 670 if self.scope: 671 scopes = list(self.scope) 672 scopes.sort() 673 result = " ".join([result] + scopes) 674 675 return result
676 677
678 -class MailactionDescriptor(BasePremapDescriptor):
679 """ Mail action parsing and storage """ 680
681 - def doTransform(self, value, arg):
682 """ Parse the mail action """ 683 if not value: 684 value = None 685 elif isinstance(value, _MailAction): 686 pass 687 elif isinstance(value, str): 688 value = _MailAction(value) 689 else: 690 raise TypeError( 691 "Supplied value must be a string, not %r" % 692 type(value).__name__ 693 ) 694 695 return value
696 697 698 # Define the typemap 699 typemap = util.ReadOnlyDict({ 700 'unicode' : UnicodeDescriptor, 701 'string' : StringDescriptor, 702 'int' : IntegerDescriptor, 703 'bool' : BooleanDescriptor, 704 'humanbool' : HumanBooleanDescriptor, 705 'regex' : RegexDescriptor, 706 'token' : TokenDescriptor, 707 'tokenlist' : TokenlistDescriptor, 708 'filename' : FilenameDescriptor, 709 'unicommand': CommandlineDescriptor, 710 'quotedstr' : QuotedstringDescriptor, 711 'stdin' : StdinDescriptor, 712 'mailaction': MailactionDescriptor, 713 }) 714