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

Source Code for Module Gnumed.pycommon.gmCfg2

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