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 continue 393 394 try: value = source_data[option_path] 395 except KeyError: 396 _log.debug('option [%s] not in group [%s] in source [%s]', option, group, source) 397 continue 398 _log.debug('option [%s] found in source [%s]', option_path, source) 399 400 if policy == 'return': 401 return value 402 403 if policy == 'extend': 404 if isinstance(value, type([])): 405 results.extend(value) 406 else: 407 results.append(value) 408 else: 409 results.append(value) 410 411 if len(results) == 0: 412 return None 413 414 return results
415 416 #--------------------------------------------------
417 - def set_option(self, option=None, value=None, group=None, source=None):
418 """Set a particular option to a particular value. 419 420 Note that this does NOT PERSIST the option anywhere ! 421 """ 422 if None in [option, value]: 423 raise ValueError('neither <option> nor <value> can be None') 424 if source is None: 425 source = 'internal' 426 try: 427 self.__cfg_data[source] 428 except KeyError: 429 self.__cfg_data[source] = {} 430 if group is None: 431 group = source 432 option_path = '%s::%s' % (group, option) 433 self.__cfg_data[source][option_path] = value
434 #-------------------------------------------------- 435 # API: source related 436 #--------------------------------------------------
437 - def add_stream_source(self, source=None, stream=None, encoding=None):
438 data = parse_INI_stream(stream = stream, encoding = encoding) 439 if source in self.__cfg_data: 440 _log.warning('overriding source <%s> with [%s]', source, stream) 441 442 self.__cfg_data[source] = data
443 #--------------------------------------------------
444 - def add_file_source(self, source=None, file=None, encoding='utf8'):
445 """Add a source (a file) to the instance.""" 446 447 _log.info('file source "%s": %s (%s)', source, file, encoding) 448 449 for existing_source, existing_file in self.source_files.items(): 450 if existing_file == file: 451 if source != existing_source: 452 _log.warning('file [%s] already known as source [%s]', file, existing_source) 453 _log.warning('adding it as source [%s] may provoke trouble', source) 454 455 cfg_file = None 456 if file is not None: 457 try: 458 cfg_file = io.open(file, mode = 'rt', encoding = encoding) 459 except IOError: 460 _log.error('cannot open [%s], keeping as dummy source', file) 461 462 if cfg_file is None: 463 file = None 464 if source in self.__cfg_data: 465 _log.warning('overriding source <%s> with dummy', source) 466 self.__cfg_data[source] = {} 467 else: 468 self.add_stream_source(source = source, stream = cfg_file) 469 cfg_file.close() 470 471 self.source_files[source] = file
472 #--------------------------------------------------
473 - def remove_source(self, source):
474 """Remove a source from the instance.""" 475 476 _log.info('removing source <%s>', source) 477 478 try: 479 del self.__cfg_data[source] 480 except KeyError: 481 _log.warning("source <%s> doesn't exist", source) 482 483 try: 484 del self.source_files[source] 485 except KeyError: 486 pass
487 #--------------------------------------------------
488 - def reload_file_source(self, file=None, encoding='utf8'):
489 if file not in self.source_files.values(): 490 return 491 492 for src, fname in self.source_files.items(): 493 if fname == file: 494 self.add_file_source(source = src, file = fname, encoding = encoding)
495 # don't break the loop because there could be other sources 496 # with the same file (not very reasonable, I know) 497 #break 498 #--------------------------------------------------
499 - def add_cli(self, short_options='', long_options=None):
500 """Add command line parameters to config data. 501 502 short: 503 string containing one-letter options such as u'h?' for -h -? 504 long: 505 list of strings 506 'conf-file=' -> --conf-file=<...> 507 'debug' -> --debug 508 """ 509 _log.info('adding command line arguments') 510 _log.debug('raw command line is:') 511 _log.debug('%s', sys.argv) 512 513 import getopt 514 515 if long_options is None: 516 long_options = [] 517 518 opts, remainder = getopt.gnu_getopt ( 519 sys.argv[1:], 520 short_options, 521 long_options 522 ) 523 524 data = {} 525 for opt, val in opts: 526 if val == '': 527 data['%s::%s' % ('cli', opt)] = True 528 else: 529 data['%s::%s' % ('cli', opt)] = val 530 531 self.__cfg_data['cli'] = data
532 #================================================================== 533 # main 534 #================================================================== 535 if __name__ == "__main__": 536 537 if len(sys.argv) < 2: 538 sys.exit() 539 540 if sys.argv[1] != 'test': 541 sys.exit() 542 543 logging.basicConfig(level = logging.DEBUG) 544 #-----------------------------------------
545 - def test_gmCfgData():
546 cfg = gmCfgData() 547 cfg.add_cli(short_options='h?', long_options=['help', 'conf-file=']) 548 cfg.set_option('internal option', True) 549 print (cfg.get(option = '--help', source_order = [('cli', 'return')])) 550 print (cfg.get(option = '-?', source_order = [('cli', 'return')])) 551 fname = cfg.get(option = '--conf-file', source_order = [('cli', 'return')]) 552 if fname is not None: 553 cfg.add_file_source(source = 'explicit', file = fname)
554 #-----------------------------------------
555 - def test_set_list_opt():
556 src = [ 557 '# a comment', 558 '', 559 '[empty group]', 560 '[second group]', 561 'some option = in second group', 562 '# another comment', 563 '[test group]', 564 '', 565 'test list = $test list$', 566 'old 1', 567 'old 2', 568 '$test list$', 569 '# another group:', 570 '[dummy group]' 571 ] 572 573 __set_list_in_INI_file ( 574 src = src, 575 sink = sys.stdout, 576 group = 'test group', 577 option = 'test list', 578 value = list('123') 579 )
580 #-----------------------------------------
581 - def test_set_opt():
582 src = [ 583 '# a comment', 584 '[empty group]', 585 '# another comment', 586 '', 587 '[second group]', 588 'some option = in second group', 589 '', 590 '[trap group]', 591 'trap list = $trap list$', 592 'dummy 1', 593 'test option = a trap', 594 'dummy 2', 595 '$trap list$', 596 '', 597 '[test group]', 598 'test option = for real (old)', 599 '' 600 ] 601 602 __set_opt_in_INI_file ( 603 src = src, 604 sink = sys.stdout, 605 group = 'test group', 606 option = 'test option', 607 value = 'for real (new)' 608 )
609 #----------------------------------------- 610 #test_gmCfgData() 611 test_set_list_opt() 612 #test_set_opt() 613 614 #================================================================== 615