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