Package translate :: Package misc :: Module ini

[hide private]
[frames] | no frames]

# Source Code for Module translate.misc.ini

      1  # Copyright (c) 2001, 2002, 2003 Python Software Foundation
      2  # Copyright (c) 2004 Paramjit Oberoi <param.cs.wisc.edu>
      3  # All Rights Reserved.  See LICENSE-PSF & LICENSE for details.
      4
      5  """Access and/or modify INI files
      6
      7  * Compatiable with ConfigParser
      8  * Preserves order of sections & options
      9  * Preserves comments/blank lines/etc
     10  * More convenient access to data
     11
     12  Example:
     13
     14      >>> from StringIO import StringIO
     15      >>> sio = StringIO('''# configure foo-application
     16      ... [foo]
     17      ... bar1 = qualia
     18      ... bar2 = 1977
     19      ... [foo-ext]
     20      ... special = 1''')
     21
     22      >>> cfg = INIConfig(sio)
     23      >>> print cfg.foo.bar1
     24      qualia
     25      >>> print cfg['foo-ext'].special
     26      1
     27      >>> cfg.foo.newopt = 'hi!'
     28
     29      >>> print cfg
     30      # configure foo-application
     31      [foo]
     32      bar1 = qualia
     33      bar2 = 1977
     34      newopt = hi!
     35      [foo-ext]
     36      special = 1
     37
     38  """
     39
     40  # An ini parser that supports ordered sections/options
     41  # Also supports updates, while preserving structure
     42  # Backward-compatiable with ConfigParser
     43
     44  import re
     45  from sets import Set
     46
     47  from iniparse import config
     48  from ConfigParser import DEFAULTSECT, ParsingError, MissingSectionHeaderError
     49
     50 -class LineType(object):
     51      line = None
     52
     53 -    def __init__(self, line=None):
     54          if line is not None:
     55              self.line = line.strip('\n')
     56
     57      # Return the original line for unmodified objects
     58      # Otherwise construct using the current attribute values
     59 -    def __str__(self):
     60          if self.line is not None:
     61              return self.line
     62          else:
     63              return self.to_string()
     64
     65      # If an attribute is modified after initialization
     66      # set line to None since it is no longer accurate.
     67 -    def __setattr__(self, name, value):
     68          if hasattr(self,name):
     69              self.__dict__['line'] = None
     70          self.__dict__[name] = value
     71
     72 -    def to_string(self):
     73          raise Exception('This method must be overridden in derived classes')
     74
     75
     76 -class SectionLine(LineType):
     77      regex =  re.compile(r'^\['
     78                          r'(?P<name>[^]]+)'
     79                          r'\]\s*'
     80                          r'((?P<csep>;|#)(?P<comment>.*))?$')
     81
     82 -    def __init__(self, name, comment=None, comment_separator=None,
     83                               comment_offset=-1, line=None):
     84          super(SectionLine, self).__init__(line)
     85          self.name = name
     86          self.comment = comment
     87          self.comment_separator = comment_separator
     88          self.comment_offset = comment_offset
     89
     90 -    def to_string(self):
     91          out = '[' + self.name + ']'
     92          if self.comment is not None:
     93              # try to preserve indentation of comments
     94              out = (out+' ').ljust(self.comment_offset)
     95              out = out + self.comment_separator + self.comment
     96          return out
     97
     98 -    def parse(cls, line):
     99          m = cls.regex.match(line.rstrip())
    100          if m is None:
    101              return None
    102          return cls(m.group('name'), m.group('comment'),
    103                     m.group('csep'), m.start('csep'),
    104                     line)
    105      parse = classmethod(parse)
    106
    107
    108 -class OptionLine(LineType):
    109 -    def __init__(self, name, value, separator=' = ', comment=None,
    110                   comment_separator=None, comment_offset=-1, line=None):
    111          super(OptionLine, self).__init__(line)
    112          self.name = name
    113          self.value = value
    114          self.separator = separator
    115          self.comment = comment
    116          self.comment_separator = comment_separator
    117          self.comment_offset = comment_offset
    118
    119 -    def to_string(self):
    120          out = '%s%s%s' % (self.name, self.separator, self.value)
    121          if self.comment is not None:
    122              # try to preserve indentation of comments
    123              out = (out+' ').ljust(self.comment_offset)
    124              out = out + self.comment_separator + self.comment
    125          return out
    126
    127      regex = re.compile(r'^(?P<name>[^:=\s[][^:=\s]*)'
    128                         r'(?P<sep>\s*[:=]\s*)'
    129                         r'(?P<value>.*)$')
    130
    131 -    def parse(cls, line):
    132          m = cls.regex.match(line.rstrip())
    133          if m is None:
    134              return None
    135
    136          name = m.group('name').rstrip()
    137          value = m.group('value')
    138          sep = m.group('sep')
    139
    140          # comments are not detected in the regex because
    141          # ensuring total compatibility with ConfigParser
    142          # requires that:
    143          #     option = value    ;comment   // value=='value'
    144          #     option = value;1  ;comment   // value=='value;1  ;comment'
    145          #
    146          # Doing this in a regex would be complicated.  I
    147          # think this is a bug.  The whole issue of how to
    148          # include ';' in the value needs to be addressed.
    149          # Also, '#' doesn't mark comments in options...
    150
    151          coff = value.find(';')
    152          if coff != -1 and value[coff-1].isspace():
    153              comment = value[coff+1:]
    154              csep = value[coff]
    155              value = value[:coff].rstrip()
    156              coff = m.start('value') + coff
    157          else:
    158              comment = None
    159              csep = None
    160              coff = -1
    161
    162          return cls(name, value, sep, comment, csep, coff, line)
    163      parse = classmethod(parse)
    164
    165
    166 -class CommentLine(LineType):
    167      regex = re.compile(r'^(?P<csep>[;#]|[rR][eE][mM])'
    168                         r'(?P<comment>.*)$')
    169
    170 -    def __init__(self, comment='', separator='#', line=None):
    171          super(CommentLine, self).__init__(line)
    172          self.comment = comment
    173          self.separator = separator
    174
    175 -    def to_string(self):
    176          return self.separator + self.comment
    177
    178 -    def parse(cls, line):
    179          m = cls.regex.match(line.rstrip())
    180          if m is None:
    181              return None
    182          return cls(m.group('comment'), m.group('csep'), line)
    183      parse = classmethod(parse)
    184
    185
    186 -class EmptyLine(LineType):
    187      # could make this a singleton
    188 -    def to_string(self):
    189          return ''
    190
    191 -    def parse(cls, line):
    192          if line.strip(): return None
    193          return cls(line)
    194      parse = classmethod(parse)
    195
    196
    197 -class ContinuationLine(LineType):
    198      regex = re.compile(r'^\s+(?P<value>.*)$')
    199
    200 -    def __init__(self, value, value_offset=8, line=None):
    201          super(ContinuationLine, self).__init__(line)
    202          self.value = value
    203          self.value_offset = value_offset
    204
    205 -    def to_string(self):
    206          return ' '*self.value_offset + self.value
    207
    208 -    def parse(cls, line):
    209          m = cls.regex.match(line.rstrip())
    210          if m is None:
    211              return None
    212          return cls(m.group('value'), m.start('value'), line)
    213      parse = classmethod(parse)
    214
    215
    216 -class LineContainer(object):
    217 -    def __init__(self, d=None):
    218          self.contents = []
    219          self.orgvalue = None
    220          if d:
    221              if isinstance(d, list): self.extend(d)
    222              else: self.add(d)
    223
    224 -    def add(self, x):
    225          self.contents.append(x)
    226
    227 -    def extend(self, x):
    228          for i in x: self.add(i)
    229
    230 -    def get_name(self):
    231          return self.contents[0].name
    232
    233 -    def set_name(self, data):
    234          self.contents[0].name = data
    235
    236 -    def get_value(self):
    237          if self.orgvalue is not None:
    238              return self.orgvalue
    239          elif len(self.contents) == 1:
    240              return self.contents[0].value
    241          else:
    242              return '\n'.join([str(x.value) for x in self.contents
    243                                if not isinstance(x, (CommentLine, EmptyLine))])
    244
    245 -    def set_value(self, data):
    246          self.orgvalue = data
    247          lines = str(data).split('\n')
    248          linediff = len(lines) - len(self.contents)
    249          if linediff > 0:
    250              for _ in range(linediff):
    251                  self.add(ContinuationLine(''))
    252          elif linediff < 0:
    253              self.contents = self.contents[:linediff]
    254          for i,v in enumerate(lines):
    255              self.contents[i].value = v
    256
    257      name = property(get_name, set_name)
    258      value = property(get_value, set_value)
    259
    260 -    def __str__(self):
    261          s = [str(x) for x in self.contents]
    262          return '\n'.join(s)
    263
    264 -    def finditer(self, key):
    265          for x in self.contents[::-1]:
    266              if hasattr(x, 'name') and x.name==key:
    267                  yield x
    268
    269 -    def find(self, key):
    270          for x in self.finditer(key):
    271              return x
    272          raise KeyError(key)
    273
    274
    275 -def _make_xform_property(myattrname, srcattrname=None):
    276      private_attrname = myattrname + 'value'
    277      private_srcname = myattrname + 'source'
    278      if srcattrname is None:
    279          srcattrname = myattrname
    280
    281      def getfn(self):
    282          srcobj = getattr(self, private_srcname)
    283          if srcobj is not None:
    284              return getattr(srcobj, srcattrname)
    285          else:
    286              return getattr(self, private_attrname)
    287
    288      def setfn(self, value):
    289          srcobj = getattr(self, private_srcname)
    290          if srcobj is not None:
    291              setattr(srcobj, srcattrname, value)
    292          else:
    293              setattr(self, private_attrname, value)
    294
    295      return property(getfn, setfn)
    296
    297
    298 -class INISection(config.ConfigNamespace):
    299      _lines = None
    300      _options = None
    301      _defaults = None
    302      _optionxformvalue = None
    303      _optionxformsource = None
    304 -    def __init__(self, lineobj, defaults = None,
    305                         optionxformvalue=None, optionxformsource=None):
    306          self._lines = [lineobj]
    307          self._defaults = defaults
    308          self._optionxformvalue = optionxformvalue
    309          self._optionxformsource = optionxformsource
    310          self._options = {}
    311
    312      _optionxform = _make_xform_property('_optionxform')
    313
    314 -    def __getitem__(self, key):
    315          if key == '__name__':
    316              return self._lines[-1].name
    317          if self._optionxform: key = self._optionxform(key)
    318          try:
    319              return self._options[key].value
    320          except KeyError:
    321              if self._defaults and key in self._defaults._options:
    322                  return self._defaults._options[key].value
    323              else:
    324                  raise
    325
    326 -    def __setitem__(self, key, value):
    327          if self._optionxform: xkey = self._optionxform(key)
    328          else: xkey = key
    329          if xkey not in self._options:
    330              # create a dummy object - value may have multiple lines
    331              obj = LineContainer(OptionLine(key, ''))
    332              self._lines[-1].add(obj)
    333              self._options[xkey] = obj
    334          # the set_value() function in LineContainer
    335          # automatically handles multi-line values
    336          self._options[xkey].value = value
    337
    338 -    def __delitem__(self, key):
    339          if self._optionxform: key = self._optionxform(key)
    340          for l in self._lines:
    341              remaining = []
    342              for o in l.contents:
    343                  if isinstance(o, LineContainer):
    344                      n = o.name
    345                      if self._optionxform: n = self._optionxform(n)
    346                      if key != n: remaining.append(o)
    347                  else:
    348                      remaining.append(o)
    349              l.contents = remaining
    350          del self._options[key]
    351
    352 -    def __iter__(self):
    353          d = Set()
    354          for l in self._lines:
    355              for x in l.contents:
    356                  if isinstance(x, LineContainer):
    357                      if self._optionxform:
    358                          ans = self._optionxform(x.name)
    359                      else:
    360                          ans = x.name
    361                      if ans not in d:
    362                          yield ans
    363                          d.add(ans)
    364          if self._defaults:
    365              for x in self._defaults:
    366                  if x not in d:
    367                      yield x
    368                      d.add(x)
    369
    370 -    def new_namespace(self, name):
    371          raise Exception('No sub-sections allowed', name)
    372
    373
    374 -def make_comment(line):
    375      return CommentLine(line.rstrip())
    376
    377
    378 -def readline_iterator(f):
    379      """iterate over a file by only using the file object's readline method"""
    380
    381      have_newline = False
    382      while True:
    383          line = f.readline()
    384
    385          if not line:
    386              if have_newline:
    387                  yield ""
    388              return
    389
    390          if line.endswith('\n'):
    391              have_newline = True
    392          else:
    393              have_newline = False
    394
    395          yield line
    396
    397
    398 -class INIConfig(config.ConfigNamespace):
    399      _data = None
    400      _sections = None
    401      _defaults = None
    402      _optionxformvalue = None
    403      _optionxformsource = None
    404      _sectionxformvalue = None
    405      _sectionxformsource = None
    406      _parse_exc = None
    407 -    def __init__(self, fp=None, defaults = None, parse_exc=True,
    408                   optionxformvalue=str.lower, optionxformsource=None,
    409                   sectionxformvalue=None, sectionxformsource=None):
    410          self._data = LineContainer()
    411          self._parse_exc = parse_exc
    412          self._optionxformvalue = optionxformvalue
    413          self._optionxformsource = optionxformsource
    414          self._sectionxformvalue = sectionxformvalue
    415          self._sectionxformsource = sectionxformsource
    416          self._sections = {}
    417          if defaults is None: defaults = {}
    418          self._defaults = INISection(LineContainer(), optionxformsource=self)
    419          for name, value in defaults.iteritems():
    420              self._defaults[name] = value
    421          if fp is not None:
    422              self.readfp(fp)
    423
    424      _optionxform = _make_xform_property('_optionxform', 'optionxform')
    425      _sectionxform = _make_xform_property('_sectionxform', 'optionxform')
    426
    427 -    def __getitem__(self, key):
    428          if key == DEFAULTSECT:
    429              return self._defaults
    430          if self._sectionxform: key = self._sectionxform(key)
    431          return self._sections[key]
    432
    433 -    def __setitem__(self, key, value):
    434          raise Exception('Values must be inside sections', key, value)
    435
    436 -    def __delitem__(self, key):
    437          if self._sectionxform: key = self._sectionxform(key)
    438          for line in self._sections[key]._lines:
    439              self._data.contents.remove(line)
    440          del self._sections[key]
    441
    442 -    def __iter__(self):
    443          d = Set()
    444          for x in self._data.contents:
    445              if isinstance(x, LineContainer):
    446                  if x.name not in d:
    447                      yield x.name
    448                      d.add(x.name)
    449
    450 -    def new_namespace(self, name):
    451          if self._data.contents:
    452              self._data.add(EmptyLine())
    453          obj = LineContainer(SectionLine(name))
    454          self._data.add(obj)
    455          if self._sectionxform: name = self._sectionxform(name)
    456          if name in self._sections:
    457              ns = self._sections[name]
    458              ns._lines.append(obj)
    459          else:
    460              ns = INISection(obj, defaults=self._defaults,
    461                              optionxformsource=self)
    462              self._sections[name] = ns
    463          return ns
    464
    465 -    def __str__(self):
    466          return str(self._data)
    467
    468      _line_types = [EmptyLine, CommentLine,
    469                     SectionLine, OptionLine,
    470                     ContinuationLine]
    471
    472 -    def _parse(self, line):
    473          for linetype in self._line_types:
    474              lineobj = linetype.parse(line)
    475              if lineobj:
    476                  return lineobj
    477          else:
    478              # can't parse line
    479              return None
    480
    481 -    def readfp(self, fp):
    482          cur_section = None
    483          cur_option = None
    484          cur_section_name = None
    485          cur_option_name = None
    486          pending_lines = []
    487          try:
    488              fname = fp.name
    489          except AttributeError:
    490              fname = '<???>'
    491          linecount = 0
    492          exc = None
    493          line = None
    494
    495          for line in readline_iterator(fp):
    496              lineobj = self._parse(line)
    497              linecount += 1
    498
    499              if not cur_section and not isinstance(lineobj,
    500                                  (CommentLine, EmptyLine, SectionLine)):
    501                  if self._parse_exc:
    502                      raise MissingSectionHeaderError(fname, linecount, line)
    503                  else:
    504                      lineobj = make_comment(line)
    505
    506              if lineobj is None:
    507                  if self._parse_exc:
    508                      if exc is None: exc = ParsingError(fname)
    509                      exc.append(linecount, line)
    510                  lineobj = make_comment(line)
    511
    512              if isinstance(lineobj, ContinuationLine):
    513                  if cur_option:
    514                      cur_option.extend(pending_lines)
    515                      pending_lines = []
    516                      cur_option.add(lineobj)
    517                  else:
    518                      # illegal continuation line - convert to comment
    519                      if self._parse_exc:
    520                          if exc is None: exc = ParsingError(fname)
    521                          exc.append(linecount, line)
    522                      lineobj = make_comment(line)
    523
    524              if isinstance(lineobj, OptionLine):
    525                  cur_section.extend(pending_lines)
    526                  pending_lines = []
    527                  cur_option = LineContainer(lineobj)
    528                  cur_section.add(cur_option)
    529                  if self._optionxform:
    530                      cur_option_name = self._optionxform(cur_option.name)
    531                  else:
    532                      cur_option_name = cur_option.name
    533                  if cur_section_name == DEFAULTSECT:
    534                      optobj = self._defaults
    535                  else:
    536                      optobj = self._sections[cur_section_name]
    537                  optobj._options[cur_option_name] = cur_option
    538
    539              if isinstance(lineobj, SectionLine):
    540                  self._data.extend(pending_lines)
    541                  pending_lines = []
    542                  cur_section = LineContainer(lineobj)
    543                  self._data.add(cur_section)
    544                  cur_option = None
    545                  cur_option_name = None
    546                  if cur_section.name == DEFAULTSECT:
    547                      self._defaults._lines.append(cur_section)
    548                      cur_section_name = DEFAULTSECT
    549                  else:
    550                      if self._sectionxform:
    551                          cur_section_name = self._sectionxform(cur_section.name)
    552                      else:
    553                          cur_section_name = cur_section.name
    554                      if cur_section_name not in self._sections:
    555                          self._sections[cur_section_name] = \
    556                                  INISection(cur_section, defaults=self._defaults,
    557                                             optionxformsource=self)
    558                      else:
    559                          self._sections[cur_section_name]._lines.append(cur_section)
    560
    561              if isinstance(lineobj, (CommentLine, EmptyLine)):
    562                  pending_lines.append(lineobj)
    563
    564          self._data.extend(pending_lines)
    565          if line and line[-1]=='\n':
    566              self._data.add(EmptyLine())
    567
    568          if exc:
    569              raise exc
    570