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