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