Gnumed.pycommon.gmCfg2

Package Gnumed :: Package pycommon :: Module gmCfg2 |
[frames] | no frames]

# Source Code for Module Gnumed.pycommon.gmCfg2

      1  """GNUmed configuration handling.
      2  """
      3  #==================================================================
      4  __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
      5  __licence__ = "GPL"
      6
      7
      8  import logging
      9  import sys
     10  import codecs
     11  import re as regex
     12  import shutil
     13  import os
     14
     15
     16  if __name__ == "__main__":
     17          sys.path.insert(0, '../../')
     18  from Gnumed.pycommon import gmBorg
     19
     20
     21  _log = logging.getLogger('gm.cfg')
     22  #==================================================================
     23  # helper functions
     24  #==================================================================
     25 -def __set_opt_in_INI_file(src=None, sink=None, group=None, option=None, value=None):
     26
     27          group_seen = False
     28          option_seen = False
     29          in_list = False
     30
     31          for line in src:
     32
     33                  # after option already ?
     34                  if option_seen:
     35                          sink.write(line)
     36                          continue
     37
     38                  # start of list ?
     39                  if regex.match('(?P<list_name>.+)(\s|\t)*=(\s|\t)*\$(?P=list_name)\$', line) is not None:
     40                          in_list = True
     41                          sink.write(line)
     42                          continue
     43
     44                  # end of list ?
     45                  if regex.match('\$.+\$.*', line) is not None:
     46                          in_list = False
     47                          sink.write(line)
     48                          continue
     49
     50                  # our group ?
     51                  if line.strip() == u'[%s]' % group:
     52                          group_seen = True
     53                          sink.write(line)
     54                          continue
     55
     56                  # another group ?
     57                  if regex.match('\[.+\].*', line) is not None:
     58                          # next group but option not seen yet ?
     59                          if group_seen and not option_seen:
     60                                  sink.write(u'%s = %s\n\n\n' % (option, value))
     61                                  option_seen = True
     62                          sink.write(line)
     63                          continue
     64
     65                  # our option ?
     66                  if regex.match('%s(\s|\t)*=' % option, line) is not None:
     67                          if group_seen:
     68                                  sink.write(u'%s = %s\n' % (option, value))
     69                                  option_seen = True
     70                                  continue
     71                          sink.write(line)
     72                          continue
     73
     74                  # something else (comment, empty line, or other option)
     75                  sink.write(line)
     76
     77          # all done ?
     78          if option_seen:
     79                  return
     80
     81          # need to add group ?
     82          if not group_seen:
     83                  sink.write('[%s]\n' % group)
     84
     85          # We either just added the group or it was the last group
     86          # but did not contain the option. It must have been the
     87          # last group then or else the following group would have
     88          # triggered the option writeout.
     89          sink.write(u'%s = %s\n' % (option, value))
     90
     91  #==================================================================
     92 -def __set_list_in_INI_file(src=None, sink=None, group=None, option=None, value=None):
     93
     94          our_group_seen = False
     95          inside_our_group = False
     96          our_list_seen = False
     97          inside_our_list = False
     98
     99          # loop until group found or src empty
    100          for line in src:
    101
    102                  if inside_our_list:                     # can only be true if already inside our group
    103                          # new list has been written already
    104                          # so now at end of our (old) list ?
    105                          if regex.match('\$%s\$' % option, line.strip()) is not None:
    106                                  inside_our_list = False
    107                                  continue
    108                          # skip old list entries
    109                          continue
    110
    111                  if inside_our_group:
    112                          # our option ?
    113                          if regex.match('%s(\s|\t)*=(\s|\t)*\$%s\$' % (option, option), line.strip()) is not None:
    114                                  sink.write(line)                                                                                # list header
    115                                  sink.write('\n'.join(value))
    116                                  sink.write('\n')
    117                                  sink.write('$%s$\n' % option)                                                   # list footer
    118                                  our_list_seen = True
    119                                  inside_our_list = True
    120                                  continue
    121
    122                          # next group (= end of our group) ?
    123                          if regex.match('\[.+\]', line.strip()) is not None:
    124                                  # our list already handled ?  (if so must already be finished)
    125                                  if not our_list_seen:
    126                                          # no, so need to add our list to the group before ...
    127                                          sink.write('%s = $%s$\n' % (option, option))            # list header
    128                                          sink.write('\n'.join(value))
    129                                          sink.write('\n')
    130                                          sink.write('$%s$\n' % option)                                           # list footer
    131                                          our_list_seen = True
    132                                          inside_our_list = False
    133                                  # ... starting the next group
    134                                  sink.write(line)                                # next group header
    135                                  inside_our_group = False
    136                                  continue
    137
    138                          # other lines inside our group
    139                          sink.write(line)
    140                          continue
    141
    142                  # our group ?
    143                  if line.strip() == u'[%s]' % group:
    144                          our_group_seen = True
    145                          inside_our_group = True
    146                          sink.write(line)                                                # group header
    147                          continue
    148
    149                  sink.write(line)
    150
    151          # looped over all lines but did not find our group, so add group
    152          if not our_group_seen:
    153                  sink.write('[%s]\n' % group)
    154
    155          if not our_list_seen:
    156                  # We either just added the group or it was the last group
    157                  # but did not contain the option. It must have been the
    158                  # last group then or else the group following it would have
    159                  # triggered the option writeout.
    160                  sink.write('%s = $%s$\n' % (option, option))
    161                  sink.write('\n'.join(value))
    162                  sink.write('\n')
    163                  sink.write('$%s$\n' % option)
    164
    165  #==================================================================
    166 -def __set_list_in_INI_file_old(src=None, sink=None, group=None, option=None, value=None):
    167
    168          our_group_seen = False
    169          option_seen = False
    170          in_list = False
    171
    172          for line in src:
    173
    174                  # found option but still in (old) list ?
    175                  if option_seen and in_list:
    176                          # end of (old) list ?
    177                          if regex.match('\$.+\$.*', line) is not None:
    178                                  in_list = False
    179                                  sink.write(line)
    180                                  continue
    181                          continue
    182
    183                  # after option already and not in (old) list anymore ?
    184                  if option_seen and not in_list:
    185                          sink.write(line)
    186                          continue
    187
    188                  # at start of a list ?
    189                  match = regex.match('(?P<list_name>.+)(\s|\t)*=(\s|\t)*\$(?P=list_name)\$', line)
    190                  if match is not None:
    191                          in_list = True
    192                          # our list ?
    193                          if our_group_seen and (match.group('list_name') == option):
    194                                  option_seen = True
    195                                  sink.write(line)
    196                                  sink.write('\n'.join(value))
    197                                  sink.write('\n')
    198                                  continue
    199                          sink.write(line)
    200                          continue
    201
    202                  # at end of a list ?
    203                  if regex.match('\$.+\$.*', line) is not None:
    204                          in_list = False
    205                          sink.write(line)
    206                          continue
    207
    208                  # our group ?
    209                  if line.strip() == u'[%s]' % group:
    210                          sink.write(line)
    211                          our_group_seen = True
    212                          continue
    213
    214                  # another group ?
    215                  if regex.match('\[%s\].*' % group, line) is not None:
    216                          # next group but option not seen yet ?
    217                          if our_group_seen and not option_seen:
    218                                  option_seen = True
    219                                  sink.write('%s = $%s$\n' % (option, option))
    220                                  sink.write('\n'.join(value))
    221                                  sink.write('\n')
    222                                  continue
    223                          sink.write(line)
    224                          continue
    225
    226                  # something else (comment, empty line, or other option)
    227                  sink.write(line)
    228
    229          # all done ?
    230          if option_seen:
    231                  return
    232
    233          # need to add group ?
    234          if not our_group_seen:
    235                  sink.write('[%s]\n' % group)
    236
    237          # We either just added the group or it was the last group
    238          # but did not contain the option. It must have been the
    239          # last group then or else the following group would have
    240          # triggered the option writeout.
    241          sink.write('%s = $%s$\n' % (option, option))
    242          sink.write('\n'.join(value))
    243          sink.write('\n')
    244          sink.write('$%s$\n' % option)
    245  #==================================================================
    246 -def set_option_in_INI_file(filename=None, group=None, option=None, value=None, encoding='utf8'):
    247
    248          _log.debug('setting option "%s" to "%s" in group [%s]', option, value, group)
    249          _log.debug('file: %s (%s)', filename, encoding)
    250
    251          src = codecs.open(filename = filename, mode = 'rU', encoding = encoding)
    252          # FIXME: add "." right before the *name* part of filename - this
    253          # FIXME: requires proper parsing (think of /home/lala/ -> ./home/lala vs /home/lala/gnumed/.gnumed.conf)
    254          sink_name = '%s.gmCfg2.new.conf' % filename
    255          sink = codecs.open(filename = sink_name, mode = 'wb', encoding = encoding)
    256
    257          # is value a list ?
    258          if isinstance(value, type([])):
    259                  __set_list_in_INI_file(src, sink, group, option, value)
    260          else:
    261                  __set_opt_in_INI_file(src, sink, group, option, value)
    262
    263          sink.close()
    264          src.close()
    265
    266          shutil.copy2(sink_name, filename)
    267          os.remove(sink_name)
    268  #==================================================================
    269 -def parse_INI_stream(stream=None):
    270          """Parse an iterable for INI-style data.
    271
    272          Returns a dict by sections containing a dict of values per section.
    273          """
    274          _log.debug(u'parsing INI-style data stream [%s]' % stream)
    275
    276          data = {}
    277          current_group = None
    278          current_option = None
    279          current_option_path = None
    280          inside_list = False
    281          line_idx = 0
    282
    283          for line in stream:
    284                  line = line.replace(u'\015', u'').replace(u'\012', u'').strip()
    285                  line_idx += 1
    286
    287                  if inside_list:
    288                          if line == u'$%s$' % current_option:            # end of list
    289                                  inside_list = False
    290                                  continue
    291                          data[current_option_path].append(line)
    292                          continue
    293
    294                  # noise
    295                  if line == u'' or line.startswith(u'#') or line.startswith(u';'):
    296                          continue
    297
    298                  # group
    299                  if line.startswith(u'['):
    300                          if not line.endswith(u']'):
    301                                  _log.error(u'group line does not end in "]", aborting')
    302                                  _log.error(line)
    303                                  raise ValueError('INI-stream parsing error')
    304                          group = line.strip(u'[]').strip()
    305                          if group == u'':
    306                                  _log.error(u'group name is empty, aborting')
    307                                  _log.error(line)
    308                                  raise ValueError('INI-stream parsing error')
    309                          current_group = group
    310                          continue
    311
    312                  # option
    313                  if current_group is None:
    314                          _log.warning('option found before first group, ignoring')
    315                          _log.error(line)
    316                          continue
    317
    318                  name, remainder = regex.split('\s*[=:]\s*', line, maxsplit = 1)
    319                  if name == u'':
    320                          _log.error('option name empty, aborting')
    321                          _log.error(line)
    322                          raise ValueError('INI-stream parsing error')
    323
    324                  if remainder.strip() == u'':
    325                          if (u'=' not in line) and (u':' not in line):
    326                                  _log.error('missing name/value separator (= or :), aborting')
    327                                  _log.error(line)
    328                                  raise ValueError('INI-stream parsing error')
    329
    330                  current_option = name
    331                  current_option_path = '%s::%s' % (current_group, current_option)
    332                  if data.has_key(current_option_path):
    333                          _log.warning(u'duplicate option [%s]', current_option_path)
    334
    335                  value = remainder.split(u'#', 1)[0].strip()
    336
    337                  # start of list ?
    338                  if value == '$%s$' % current_option:
    339                          inside_list = True
    340                          data[current_option_path] = []
    341                          continue
    342
    343                  data[current_option_path] = value
    344
    345          if inside_list:
    346                  _log.critical('unclosed list $%s$ detected at end of config stream [%s]', current_option, stream)
    347                  raise SyntaxError('end of config stream but still in list')
    348
    349          return data
    350  #==================================================================
    351 -class gmCfgData(gmBorg.cBorg):
    352
    353 -        def __init__(self):
    354                  try:
    355                          self.__cfg_data
    356                  except AttributeError:
    357                          self.__cfg_data = {}
    358                          self.source_files = {}
    359          #--------------------------------------------------
    360 -        def get(self, group=None, option=None, source_order=None):
    361                  """Get the value of a configuration option in a config file.
    362
    363                  <source_order> the order in which config files are searched
    364                          a list of tuples (source, policy)
    365                          policy:
    366                                  return: return only this value immediately
    367                                  append: append to list of potential values to return
    368                                  extend: if the value per source happens to be a list
    369                                          extend (rather than append to) the result list
    370
    371                  returns NONE when there's no value for an option
    372                  """
    373                  if source_order is None:
    374                          source_order = [(u'internal', u'return')]
    375                  results = []
    376                  for source, policy in source_order:
    377                          if group is None:
    378                                  group = source
    379                          option_path = u'%s::%s' % (group, option)
    380                          try: source_data = self.__cfg_data[source]
    381                          except KeyError:
    382                                  _log.error('invalid config source [%s]', source)
    383                                  _log.debug('currently known sources: %s', self.__cfg_data.keys())
    384                                  #raise
    385                                  continue
    386
    387                          try: value = source_data[option_path]
    388                          except KeyError:
    389                                  _log.debug('option [%s] not in group [%s] in source [%s]', option, group, source)
    390                                  continue
    391                          _log.debug(u'option [%s] found in source [%s]', option_path, source)
    392
    393                          if policy == u'return':
    394                                  return value
    395
    396                          if policy == u'extend':
    397                                  if isinstance(value, type([])):
    398                                          results.extend(value)
    399                                  else:
    400                                          results.append(value)
    401                          else:
    402                                  results.append(value)
    403
    404                  if len(results) == 0:
    405                          return None
    406
    407                  return results
    408          #--------------------------------------------------
    409 -        def set_option(self, option=None, value=None, group=None, source=None):
    410                  """Set a particular option to a particular value.
    411
    412                  Note that this does NOT PERSIST the option anywhere !
    413                  """
    414                  if None in [option, value]:
    415                          raise ValueError('neither <option> nor <value> can be None')
    416                  if source is None:
    417                          source = u'internal'
    418                          try:
    419                                  self.__cfg_data[source]
    420                          except KeyError:
    421                                  self.__cfg_data[source] = {}
    422                  if group is None:
    423                          group = source
    424                  option_path = u'%s::%s' % (group, option)
    425                  self.__cfg_data[source][option_path] = value
    426          #--------------------------------------------------
    427          # API: source related
    428          #--------------------------------------------------
    429 -        def add_stream_source(self, source=None, stream=None):
    430
    431                  try:
    432                          data = parse_INI_stream(stream = stream)
    433                  except ValueError:
    434                          _log.exception('error parsing source <%s> from [%s]', source, stream)
    435                          raise
    436
    437                  if self.__cfg_data.has_key(source):
    438                          _log.warning('overriding source <%s> with [%s]', source, stream)
    439
    440                  self.__cfg_data[source] = data
    441          #--------------------------------------------------
    442 -        def add_file_source(self, source=None, file=None, encoding='utf8'):
    443                  """Add a source (a file) to the instance."""
    444
    445                  _log.info('file source "%s": %s (%s)', source, file, encoding)
    446
    447                  for existing_source, existing_file in self.source_files.iteritems():
    448                          if existing_file == file:
    449                                  if source != existing_source:
    450                                          _log.warning('file [%s] already known as source [%s]', file, existing_source)
    451                                          _log.warning('adding it as source [%s] may provoke trouble', source)
    452
    453                  cfg_file = None
    454                  if file is not None:
    455                          try:
    456                                  cfg_file = codecs.open(filename = file, mode = 'rU', encoding = encoding)
    457                          except IOError:
    458                                  _log.error('cannot open [%s], keeping as dummy source', file)
    459
    460                  if cfg_file is None:
    461                          file = None
    462                          if self.__cfg_data.has_key(source):
    463                                  _log.warning('overriding source <%s> with dummy', source)
    464                          self.__cfg_data[source] = {}
    465                  else:
    466                          self.add_stream_source(source = source, stream = cfg_file)
    467                          cfg_file.close()
    468
    469                  self.source_files[source] = file
    470          #--------------------------------------------------
    471 -        def remove_source(self, source):
    472                  """Remove a source from the instance."""
    473
    474                  _log.info('removing source <%s>', source)
    475
    476                  try:
    477                          del self.__cfg_data[source]
    478                  except KeyError:
    479                          _log.warning("source <%s> doesn't exist", source)
    480
    481                  try:
    482                          del self.source_files[source]
    483                  except KeyError:
    484                          pass
    485          #--------------------------------------------------
    486 -        def reload_file_source(self, file=None, encoding='utf8'):
    487                  if file not in self.source_files.values():
    488                          return
    489
    490                  for src, fname in self.source_files.iteritems():
    491                          if fname == file:
    492                                  self.add_file_source(source = src, file = fname, encoding = encoding)
    493                                  # don't break the loop because there could be other sources
    494                                  # with the same file (not very reasonable, I know)
    495                                  #break
    496          #--------------------------------------------------
    497 -        def add_cli(self, short_options=u'', long_options=None):
    498                  """Add command line parameters to config data.
    499
    500                  short:
    501                          string containing one-letter options such as u'h?' for -h -?
    502                  long:
    503                          list of strings
    504                          'conf-file=' -> --conf-file=<...>
    505                          'debug' -> --debug
    506                  """
    507                  _log.info('adding command line arguments')
    508                  _log.debug('raw command line is:')
    509                  _log.debug('%s', sys.argv)
    510
    511                  import getopt
    512
    513                  if long_options is None:
    514                          long_options = []
    515
    516                  opts, remainder = getopt.gnu_getopt (
    517                          sys.argv[1:],
    518                          short_options,
    519                          long_options
    520                  )
    521
    522                  data = {}
    523                  for opt, val in opts:
    524                          if val == u'':
    525                                  data[u'%s::%s' % (u'cli', opt)] = True
    526                          else:
    527                                  data[u'%s::%s' % (u'cli', opt)] = val
    528
    529                  self.__cfg_data[u'cli'] = data
    530  #==================================================================
    531  # main
    532  #==================================================================
    533  if __name__ == "__main__":
    534
    535          if len(sys.argv) < 2:
    536                  sys.exit()
    537
    538          if sys.argv[1] != u'test':
    539                  sys.exit()
    540
    541          logging.basicConfig(level = logging.DEBUG)
    542          #-----------------------------------------
    543 -        def test_gmCfgData():
    544                  cfg = gmCfgData()
    545                  cfg.add_cli(short_options=u'h?', long_options=[u'help', u'conf-file='])
    546                  cfg.set_option('internal option', True)
    547                  print cfg.get(option = '--help', source_order = [('cli', 'return')])
    548                  print cfg.get(option = '-?', source_order = [('cli', 'return')])
    549                  fname = cfg.get(option = '--conf-file', source_order = [('cli', 'return')])
    550                  if fname is not None:
    551                          cfg.add_file_source(source = 'explicit', file = fname)
    552          #-----------------------------------------
    553 -        def test_set_list_opt():
    554                  src = [
    555                          '# a comment',
    556                          '',
    557                          '[empty group]',
    558                          '[second group]',
    559                          'some option = in second group',
    560                          '# another comment',
    561                          '[test group]',
    562                          '',
    563                          'test list      = $test list$',
    564                          'old 1',
    565                          'old 2',
    566                          '$test list$',
    567                          '# another group:',
    568                          '[dummy group]'
    569                  ]
    570
    571                  __set_list_in_INI_file (
    572                          src = src,
    573                          sink = sys.stdout,
    574                          group = u'test group',
    575                          option = u'test list',
    576                          value = list('123')
    577                  )
    578          #-----------------------------------------
    579 -        def test_set_opt():
    580                  src = [
    581                          '# a comment',
    582                          '[empty group]',
    583                          '# another comment',
    584                          '',
    585                          '[second group]',
    586                          'some option = in second group',
    587                          '',
    588                          '[trap group]',
    589                          'trap list      = $trap list$',
    590                          'dummy 1',
    591                          'test option = a trap',
    592                          'dummy 2',
    593                          '$trap list$',
    594                          '',
    595                          '[test group]',
    596                          'test option = for real (old)',
    597                          ''
    598                  ]
    599
    600                  __set_opt_in_INI_file (
    601                          src = src,
    602                          sink = sys.stdout,
    603                          group = u'test group',
    604                          option = u'test option',
    605                          value = u'for real (new)'
    606                  )
    607          #-----------------------------------------
    608          #test_gmCfgData()
    609          test_set_list_opt()
    610          #test_set_opt()
    611
    612  #==================================================================
    613