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