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

Source Code for Module Gnumed.pycommon.gmTools

   1  # -*- coding: utf8 -*- 
   2  __doc__ = """GNUmed general tools.""" 
   3   
   4  #=========================================================================== 
   5  __author__ = "K. Hilbert <Karsten.Hilbert@gmx.net>" 
   6  __license__ = "GPL (details at http://www.gnu.org)" 
   7   
   8  # std libs 
   9  import re as regex, sys, os, os.path, csv, tempfile, logging, hashlib 
  10  import decimal 
  11  import cPickle, zlib 
  12   
  13   
  14  # GNUmed libs 
  15  if __name__ == '__main__': 
  16          # for testing: 
  17          logging.basicConfig(level = logging.DEBUG) 
  18          sys.path.insert(0, '../../') 
  19          from Gnumed.pycommon import gmI18N 
  20          gmI18N.activate_locale() 
  21          gmI18N.install_domain() 
  22   
  23  from Gnumed.pycommon import gmBorg 
  24   
  25   
  26  _log = logging.getLogger('gm.tools') 
  27   
  28  # CAPitalization modes: 
  29  (       CAPS_NONE,                                      # don't touch it 
  30          CAPS_FIRST,                                     # CAP first char, leave rest as is 
  31          CAPS_ALLCAPS,                           # CAP all chars 
  32          CAPS_WORDS,                                     # CAP first char of every word 
  33          CAPS_NAMES,                                     # CAP in a way suitable for names (tries to be smart) 
  34          CAPS_FIRST_ONLY                         # CAP first char, lowercase the rest 
  35  ) = range(6) 
  36   
  37   
  38  u_right_double_angle_quote = u'\u00AB'          # << 
  39  u_registered_trademark = u'\u00AE' 
  40  u_plus_minus = u'\u00B1' 
  41  u_left_double_angle_quote = u'\u00BB'           # >> 
  42  u_one_quarter = u'\u00BC' 
  43  u_one_half = u'\u00BD' 
  44  u_three_quarters = u'\u00BE' 
  45  u_ellipsis = u'\u2026' 
  46  u_down_left_arrow = u'\u21B5'                           # <-' 
  47  u_left_arrow = u'\u2190'                                        # <-- 
  48  u_right_arrow = u'\u2192'                                       # --> 
  49  u_sum = u'\u2211' 
  50  u_corresponds_to = u'\u2258' 
  51  u_infinity = u'\u221E' 
  52  u_diameter = u'\u2300' 
  53  u_checkmark_crossed_out = u'\u237B' 
  54  u_box_horiz_single = u'\u2500' 
  55  u_box_horiz_4dashes = u'\u2508' 
  56  u_box_top_double = u'\u2550' 
  57  u_box_top_left_double_single = u'\u2552' 
  58  u_box_top_right_double_single = u'\u2555' 
  59  u_box_top_left_arc = u'\u256d' 
  60  u_box_bottom_right_arc = u'\u256f' 
  61  u_box_bottom_left_arc = u'\u2570' 
  62  u_box_horiz_light_heavy = u'\u257c' 
  63  u_box_horiz_heavy_light = u'\u257e' 
  64  u_frowning_face = u'\u2639' 
  65  u_smiling_face = u'\u263a' 
  66  u_black_heart = u'\u2665' 
  67  u_checkmark_thin = u'\u2713' 
  68  u_checkmark_thick = u'\u2714' 
  69  u_writing_hand = u'\u270d' 
  70  u_pencil_1 = u'\u270e' 
  71  u_pencil_2 = u'\u270f' 
  72  u_pencil_3 = u'\u2710' 
  73  u_latin_cross = u'\u271d' 
  74  u_replacement_character = u'\ufffd' 
  75   
  76   
  77  #=========================================================================== 
78 -def handle_uncaught_exception_console(t, v, tb):
79 80 print ".========================================================" 81 print "| Unhandled exception caught !" 82 print "| Type :", t 83 print "| Value:", v 84 print "`========================================================" 85 _log.critical('unhandled exception caught', exc_info = (t,v,tb)) 86 sys.__excepthook__(t,v,tb)
87 #=========================================================================== 88 # path level operations 89 #---------------------------------------------------------------------------
90 -def mkdir(directory=None):
91 try: 92 os.makedirs(directory) 93 except OSError, e: 94 if (e.errno == 17) and not os.path.isdir(directory): 95 raise 96 return True
97 98 #---------------------------------------------------------------------------
99 -class gmPaths(gmBorg.cBorg):
100 """This class provides the following paths: 101 102 .home_dir 103 .local_base_dir 104 .working_dir 105 .user_config_dir 106 .system_config_dir 107 .system_app_data_dir 108 .tmp_dir (only readable) 109 """
110 - def __init__(self, app_name=None, wx=None):
111 """Setup pathes. 112 113 <app_name> will default to (name of the script - .py) 114 """ 115 try: 116 self.already_inited 117 return 118 except AttributeError: 119 pass 120 121 self.init_paths(app_name=app_name, wx=wx) 122 self.already_inited = True
123 #-------------------------------------- 124 # public API 125 #--------------------------------------
126 - def init_paths(self, app_name=None, wx=None):
127 128 if wx is None: 129 _log.debug('wxPython not available') 130 _log.debug('detecting paths directly') 131 132 if app_name is None: 133 app_name, ext = os.path.splitext(os.path.basename(sys.argv[0])) 134 _log.info('app name detected as [%s]', app_name) 135 else: 136 _log.info('app name passed in as [%s]', app_name) 137 138 # the user home, doesn't work in Wine so work around that 139 self.__home_dir = None 140 141 # where the main script (the "binary") is installed 142 #self.local_base_dir = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '..', '.')) 143 self.local_base_dir = os.path.abspath(os.path.dirname(sys.argv[0])) 144 145 # the current working dir at the OS 146 self.working_dir = os.path.abspath(os.curdir) 147 148 # user-specific config dir, usually below the home dir 149 #mkdir(os.path.expanduser(os.path.join('~', '.%s' % app_name))) 150 #self.user_config_dir = os.path.expanduser(os.path.join('~', '.%s' % app_name)) 151 mkdir(os.path.join(self.home_dir, '.%s' % app_name)) 152 self.user_config_dir = os.path.join(self.home_dir, '.%s' % app_name) 153 154 # system-wide config dir, usually below /etc/ under UN*X 155 try: 156 self.system_config_dir = os.path.join('/etc', app_name) 157 except ValueError: 158 #self.system_config_dir = self.local_base_dir 159 self.system_config_dir = self.user_config_dir 160 161 # system-wide application data dir 162 try: 163 self.system_app_data_dir = os.path.join(sys.prefix, 'share', app_name) 164 except ValueError: 165 self.system_app_data_dir = self.local_base_dir 166 167 # temporary directory 168 try: 169 self.__tmp_dir_already_set 170 _log.debug('temp dir already set') 171 except AttributeError: 172 tmp_base = os.path.join(tempfile.gettempdir(), app_name) 173 mkdir(tmp_base) 174 _log.info('previous temp dir: %s', tempfile.gettempdir()) 175 tempfile.tempdir = tmp_base 176 _log.info('intermediate temp dir: %s', tempfile.gettempdir()) 177 self.tmp_dir = tempfile.mkdtemp(prefix = r'gm-') 178 179 self.__log_paths() 180 if wx is None: 181 return True 182 183 # retry with wxPython 184 _log.debug('re-detecting paths with wxPython') 185 186 std_paths = wx.StandardPaths.Get() 187 _log.info('wxPython app name is [%s]', wx.GetApp().GetAppName()) 188 189 # user-specific config dir, usually below the home dir 190 mkdir(os.path.join(std_paths.GetUserConfigDir(), '.%s' % app_name)) 191 self.user_config_dir = os.path.join(std_paths.GetUserConfigDir(), '.%s' % app_name) 192 193 # system-wide config dir, usually below /etc/ under UN*X 194 try: 195 tmp = std_paths.GetConfigDir() 196 if not tmp.endswith(app_name): 197 tmp = os.path.join(tmp, app_name) 198 self.system_config_dir = tmp 199 except ValueError: 200 # leave it at what it was from direct detection 201 pass 202 203 # system-wide application data dir 204 # Robin attests that the following doesn't always 205 # give sane values on Windows, so IFDEF it 206 if 'wxMSW' in wx.PlatformInfo: 207 _log.warning('this platform (wxMSW) sometimes returns a broken value for the system-wide application data dir') 208 else: 209 try: 210 self.system_app_data_dir = std_paths.GetDataDir() 211 except ValueError: 212 pass 213 214 self.__log_paths() 215 return True
216 #--------------------------------------
217 - def __log_paths(self):
218 _log.debug('sys.argv[0]: %s', sys.argv[0]) 219 _log.debug('local application base dir: %s', self.local_base_dir) 220 _log.debug('current working dir: %s', self.working_dir) 221 #_log.debug('user home dir: %s', os.path.expanduser('~')) 222 _log.debug('user home dir: %s', self.home_dir) 223 _log.debug('user-specific config dir: %s', self.user_config_dir) 224 _log.debug('system-wide config dir: %s', self.system_config_dir) 225 _log.debug('system-wide application data dir: %s', self.system_app_data_dir) 226 _log.debug('temporary dir: %s', self.tmp_dir)
227 #-------------------------------------- 228 # properties 229 #--------------------------------------
230 - def _set_user_config_dir(self, path):
231 if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)): 232 msg = '[%s:user_config_dir]: invalid path [%s]' % (self.__class__.__name__, path) 233 _log.error(msg) 234 raise ValueError(msg) 235 self.__user_config_dir = path
236
237 - def _get_user_config_dir(self):
238 return self.__user_config_dir
239 240 user_config_dir = property(_get_user_config_dir, _set_user_config_dir) 241 #--------------------------------------
242 - def _set_system_config_dir(self, path):
243 if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)): 244 msg = '[%s:system_config_dir]: invalid path [%s]' % (self.__class__.__name__, path) 245 _log.error(msg) 246 raise ValueError(msg) 247 self.__system_config_dir = path
248
249 - def _get_system_config_dir(self):
250 return self.__system_config_dir
251 252 system_config_dir = property(_get_system_config_dir, _set_system_config_dir) 253 #--------------------------------------
254 - def _set_system_app_data_dir(self, path):
255 if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)): 256 msg = '[%s:system_app_data_dir]: invalid path [%s]' % (self.__class__.__name__, path) 257 _log.error(msg) 258 raise ValueError(msg) 259 self.__system_app_data_dir = path
260
261 - def _get_system_app_data_dir(self):
262 return self.__system_app_data_dir
263 264 system_app_data_dir = property(_get_system_app_data_dir, _set_system_app_data_dir) 265 #--------------------------------------
266 - def _set_home_dir(self, path):
267 raise ValueError('invalid to set home dir')
268
269 - def _get_home_dir(self):
270 if self.__home_dir is not None: 271 return self.__home_dir 272 273 tmp = os.path.expanduser('~') 274 if tmp == '~': 275 _log.error('this platform does not expand ~ properly') 276 try: 277 tmp = os.environ['USERPROFILE'] 278 except KeyError: 279 _log.error('cannot access $USERPROFILE in environment') 280 281 if not ( 282 os.access(tmp, os.R_OK) 283 and 284 os.access(tmp, os.X_OK) 285 and 286 os.access(tmp, os.W_OK) 287 ): 288 msg = '[%s:home_dir]: invalid path [%s]' % (self.__class__.__name__, tmp) 289 _log.error(msg) 290 raise ValueError(msg) 291 292 self.__home_dir = tmp 293 return self.__home_dir
294 295 home_dir = property(_get_home_dir, _set_home_dir) 296 #--------------------------------------
297 - def _set_tmp_dir(self, path):
298 if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)): 299 msg = '[%s:tmp_dir]: invalid path [%s]' % (self.__class__.__name__, path) 300 _log.error(msg) 301 raise ValueError(msg) 302 _log.debug('previous temp dir: %s', tempfile.gettempdir()) 303 self.__tmp_dir = path 304 tempfile.tempdir = self.__tmp_dir 305 self.__tmp_dir_already_set = True
306
307 - def _get_tmp_dir(self):
308 return self.__tmp_dir
309 310 tmp_dir = property(_get_tmp_dir, _set_tmp_dir)
311 #=========================================================================== 312 # file related tools 313 #---------------------------------------------------------------------------
314 -def file2md5(filename=None, return_hex=True):
315 blocksize = 2**10 * 128 # 128k, since md5 use 128 byte blocks 316 _log.debug('md5(%s): <%s> byte blocks', filename, blocksize) 317 318 f = open(filename, 'rb') 319 320 md5 = hashlib.md5() 321 while True: 322 data = f.read(blocksize) 323 if not data: 324 break 325 md5.update(data) 326 327 _log.debug('md5(%s): %s', filename, md5.hexdigest()) 328 329 if return_hex: 330 return md5.hexdigest() 331 return md5.digest()
332 #---------------------------------------------------------------------------
333 -def unicode2charset_encoder(unicode_csv_data, encoding='utf-8'):
334 for line in unicode_csv_data: 335 yield line.encode(encoding)
336 337 #def utf_8_encoder(unicode_csv_data): 338 # for line in unicode_csv_data: 339 # yield line.encode('utf-8') 340 341 default_csv_reader_rest_key = u'list_of_values_of_unknown_fields' 342
343 -def unicode_csv_reader(unicode_csv_data, dialect=csv.excel, encoding='utf-8', **kwargs):
344 345 # csv.py doesn't do Unicode; encode temporarily as UTF-8: 346 try: 347 is_dict_reader = kwargs['dict'] 348 del kwargs['dict'] 349 if is_dict_reader is not True: 350 raise KeyError 351 kwargs['restkey'] = default_csv_reader_rest_key 352 csv_reader = csv.DictReader(unicode2charset_encoder(unicode_csv_data), dialect=dialect, **kwargs) 353 except KeyError: 354 is_dict_reader = False 355 csv_reader = csv.reader(unicode2charset_encoder(unicode_csv_data), dialect=dialect, **kwargs) 356 357 for row in csv_reader: 358 # decode ENCODING back to Unicode, cell by cell: 359 if is_dict_reader: 360 for key in row.keys(): 361 if key == default_csv_reader_rest_key: 362 old_data = row[key] 363 new_data = [] 364 for val in old_data: 365 new_data.append(unicode(val, encoding)) 366 row[key] = new_data 367 if default_csv_reader_rest_key not in csv_reader.fieldnames: 368 csv_reader.fieldnames.append(default_csv_reader_rest_key) 369 else: 370 row[key] = unicode(row[key], encoding) 371 yield row 372 else: 373 yield [ unicode(cell, encoding) for cell in row ]
374 #yield [unicode(cell, 'utf-8') for cell in row] 375 #---------------------------------------------------------------------------
376 -def get_unique_filename(prefix=None, suffix=None, tmp_dir=None):
377 """This introduces a race condition between the file.close() and 378 actually using the filename. 379 380 The file will not exist after calling this function. 381 """ 382 if tmp_dir is not None: 383 if ( 384 not os.access(tmp_dir, os.F_OK) 385 or 386 not os.access(tmp_dir, os.X_OK | os.W_OK) 387 ): 388 _log.info('cannot find temporary dir [%s], using system default', tmp_dir) 389 tmp_dir = None 390 391 kwargs = {'dir': tmp_dir} 392 393 if prefix is None: 394 kwargs['prefix'] = 'gnumed-' 395 else: 396 kwargs['prefix'] = prefix 397 398 if suffix in [None, u'']: 399 kwargs['suffix'] = '.tmp' 400 else: 401 if not suffix.startswith('.'): 402 suffix = '.' + suffix 403 kwargs['suffix'] = suffix 404 405 f = tempfile.NamedTemporaryFile(**kwargs) 406 filename = f.name 407 f.close() 408 409 return filename
410 #===========================================================================
411 -def import_module_from_directory(module_path=None, module_name=None, always_remove_path=False):
412 """Import a module from any location.""" 413 414 remove_path = always_remove_path or False 415 if module_path not in sys.path: 416 _log.info('appending to sys.path: [%s]' % module_path) 417 sys.path.append(module_path) 418 remove_path = True 419 420 _log.debug('will remove import path: %s', remove_path) 421 422 if module_name.endswith('.py'): 423 module_name = module_name[:-3] 424 425 try: 426 module = __import__(module_name) 427 except StandardError: 428 _log.exception('cannot __import__() module [%s] from [%s]' % (module_name, module_path)) 429 while module_path in sys.path: 430 sys.path.remove(module_path) 431 raise 432 433 _log.info('imported module [%s] as [%s]' % (module_name, module)) 434 if remove_path: 435 while module_path in sys.path: 436 sys.path.remove(module_path) 437 438 return module
439 #=========================================================================== 440 # text related tools 441 #--------------------------------------------------------------------------- 442 _kB = 1024 443 _MB = 1024 * _kB 444 _GB = 1024 * _MB 445 _TB = 1024 * _GB 446 _PB = 1024 * _TB 447 #---------------------------------------------------------------------------
448 -def size2str(size=0, template='%s'):
449 if size == 1: 450 return template % _('1 Byte') 451 if size < 10 * _kB: 452 return template % _('%s Bytes') % size 453 if size < _MB: 454 return template % u'%.1f kB' % (float(size) / _kB) 455 if size < _GB: 456 return template % u'%.1f MB' % (float(size) / _MB) 457 if size < _TB: 458 return template % u'%.1f GB' % (float(size) / _GB) 459 if size < _PB: 460 return template % u'%.1f TB' % (float(size) / _TB) 461 return template % u'%.1f PB' % (float(size) / _PB)
462 #---------------------------------------------------------------------------
463 -def bool2subst(boolean=None, true_return=True, false_return=False, none_return=None):
464 if boolean is None: 465 return none_return 466 if boolean is True: 467 return true_return 468 if boolean is False: 469 return false_return 470 raise ValueError('bool2subst(): <boolean> arg must be either of True, False, None')
471 #---------------------------------------------------------------------------
472 -def bool2str(boolean=None, true_str='True', false_str='False'):
473 return bool2subst ( 474 boolean = bool(boolean), 475 true_return = true_str, 476 false_return = false_str 477 )
478 #---------------------------------------------------------------------------
479 -def none_if(value=None, none_equivalent=None):
480 """Modelled after the SQL NULLIF function.""" 481 if value == none_equivalent: 482 return None 483 return value
484 #---------------------------------------------------------------------------
485 -def coalesce(initial=None, instead=None, template_initial=None, template_instead=None, none_equivalents=None, function_initial=None):
486 """Modelled after the SQL coalesce function. 487 488 To be used to simplify constructs like: 489 490 if initial is None (or in none_equivalents): 491 real_value = (template_instead % instead) or instead 492 else: 493 real_value = (template_initial % initial) or initial 494 print real_value 495 496 @param initial: the value to be tested for <None> 497 @type initial: any Python type, must have a __str__ method if template_initial is not None 498 @param instead: the value to be returned if <initial> is None 499 @type instead: any Python type, must have a __str__ method if template_instead is not None 500 @param template_initial: if <initial> is returned replace the value into this template, must contain one <%s> 501 @type template_initial: string or None 502 @param template_instead: if <instead> is returned replace the value into this template, must contain one <%s> 503 @type template_instead: string or None 504 505 example: 506 function_initial = ('strftime', '%Y-%m-%d') 507 508 Ideas: 509 - list of insteads: initial, [instead, template], [instead, template], [instead, template], template_initial, ... 510 """ 511 if none_equivalents is None: 512 none_equivalents = [None] 513 514 if initial in none_equivalents: 515 516 if template_instead is None: 517 return instead 518 519 return template_instead % instead 520 521 if function_initial is not None: 522 funcname, args = function_initial 523 func = getattr(initial, funcname) 524 initial = func(args) 525 526 if template_initial is None: 527 return initial 528 529 try: 530 return template_initial % initial 531 except TypeError: 532 return template_initial
533 #---------------------------------------------------------------------------
534 -def __cap_name(match_obj=None):
535 val = match_obj.group(0).lower() 536 if val in ['von', 'van', 'de', 'la', 'l', 'der', 'den']: # FIXME: this needs to expand, configurable ? 537 return val 538 buf = list(val) 539 buf[0] = buf[0].upper() 540 for part in ['mac', 'mc', 'de', 'la']: 541 if len(val) > len(part) and val[:len(part)] == part: 542 buf[len(part)] = buf[len(part)].upper() 543 return ''.join(buf)
544 #---------------------------------------------------------------------------
545 -def capitalize(text=None, mode=CAPS_NAMES):
546 """Capitalize the first character but leave the rest alone. 547 548 Note that we must be careful about the locale, this may 549 have issues ! However, for UTF strings it should just work. 550 """ 551 if (mode is None) or (mode == CAPS_NONE): 552 return text 553 554 if mode == CAPS_FIRST: 555 if len(text) == 1: 556 return text[0].upper() 557 return text[0].upper() + text[1:] 558 559 if mode == CAPS_ALLCAPS: 560 return text.upper() 561 562 if mode == CAPS_FIRST_ONLY: 563 if len(text) == 1: 564 return text[0].upper() 565 return text[0].upper() + text[1:].lower() 566 567 if mode == CAPS_WORDS: 568 return regex.sub(ur'(\w)(\w+)', lambda x: x.group(1).upper() + x.group(2).lower(), text) 569 570 if mode == CAPS_NAMES: 571 #return regex.sub(r'\w+', __cap_name, text) 572 return capitalize(text=text, mode=CAPS_FIRST) # until fixed 573 574 print "ERROR: invalid capitalization mode: [%s], leaving input as is" % mode 575 return text
576 #---------------------------------------------------------------------------
577 -def input2decimal(initial=None):
578 579 if isinstance(initial, decimal.Decimal): 580 return True, initial 581 582 val = initial 583 584 # float ? -> to string first 585 if type(val) == type(float(1.4)): 586 val = str(val) 587 588 # string ? -> "," to "." 589 if isinstance(val, basestring): 590 val = val.replace(',', '.', 1) 591 val = val.strip() 592 593 try: 594 d = decimal.Decimal(val) 595 return True, d 596 except (TypeError, decimal.InvalidOperation): 597 return False, val
598 #---------------------------------------------------------------------------
599 -def input2int(initial=None, minval=None, maxval=None):
600 601 val = initial 602 603 # string ? -> "," to "." 604 if isinstance(val, basestring): 605 val = val.replace(',', '.', 1) 606 val = val.strip() 607 608 try: 609 int_val = int(val) 610 except (TypeError, ValueError): 611 return False, val 612 613 if minval is not None: 614 if int_val < minval: 615 return False, val 616 if maxval is not None: 617 if int_val > maxval: 618 return False, val 619 620 return True, int_val
621 #---------------------------------------------------------------------------
622 -def wrap(text=None, width=None, initial_indent=u'', subsequent_indent=u'', eol=u'\n'):
623 """A word-wrap function that preserves existing line breaks 624 and most spaces in the text. Expects that existing line 625 breaks are posix newlines (\n). 626 """ 627 wrapped = initial_indent + reduce ( 628 lambda line, word, width=width: '%s%s%s' % ( 629 line, 630 ' \n'[(len(line) - line.rfind('\n') - 1 + len(word.split('\n',1)[0]) >= width)], 631 word 632 ), 633 text.split(' ') 634 ) 635 636 if subsequent_indent != u'': 637 wrapped = (u'\n%s' % subsequent_indent).join(wrapped.split('\n')) 638 639 if eol != u'\n': 640 wrapped = wrapped.replace('\n', eol) 641 642 return wrapped
643 #---------------------------------------------------------------------------
644 -def unwrap(text=None, max_length=None, strip_whitespace=True, remove_empty_lines=True, line_separator = u' // '):
645 646 text = text.replace(u'\r', u'') 647 lines = text.split(u'\n') 648 text = u'' 649 for line in lines: 650 651 if strip_whitespace: 652 line = line.strip().strip(u'\t').strip() 653 654 if remove_empty_lines: 655 if line == u'': 656 continue 657 658 text += (u'%s%s' % (line, line_separator)) 659 660 text = text.rstrip(line_separator) 661 662 if max_length is not None: 663 text = text[:max_length] 664 665 text = text.rstrip(line_separator) 666 667 return text
668 #---------------------------------------------------------------------------
669 -def xml_escape_string(text=None):
670 """check for special XML characters and transform them""" 671 672 text = text.replace(u'&', u'&amp;') 673 674 return text
675 #---------------------------------------------------------------------------
676 -def tex_escape_string(text=None):
677 """check for special LaTeX characters and transform them""" 678 679 text = text.replace(u'\\', u'$\\backslash$') 680 text = text.replace(u'{', u'\\{') 681 text = text.replace(u'}', u'\\}') 682 text = text.replace(u'%', u'\\%') 683 text = text.replace(u'&', u'\\&') 684 text = text.replace(u'#', u'\\#') 685 text = text.replace(u'$', u'\\$') 686 text = text.replace(u'_', u'\\_') 687 688 text = text.replace(u'^', u'\\verb#^#') 689 text = text.replace('~','\\verb#~#') 690 691 return text
692 #---------------------------------------------------------------------------
693 -def prompted_input(prompt=None, default=None):
694 """Obtains entry from standard input. 695 696 prompt: Prompt text to display in standard output 697 default: Default value (for user to press enter only) 698 CTRL-C: aborts and returns None 699 """ 700 if prompt is None: 701 msg = u'(CTRL-C aborts)' 702 else: 703 msg = u'%s (CTRL-C aborts)' % prompt 704 705 if default is None: 706 msg = msg + u': ' 707 else: 708 msg = u'%s [%s]: ' % (msg, default) 709 710 try: 711 usr_input = raw_input(msg) 712 except KeyboardInterrupt: 713 return None 714 715 if usr_input == '': 716 return default 717 718 return usr_input
719 720 #=========================================================================== 721 # image handling tools 722 #--------------------------------------------------------------------------- 723 # builtin (ugly but tried and true) fallback icon 724 __icon_serpent = \ 725 """x\xdae\x8f\xb1\x0e\x83 \x10\x86w\x9f\xe2\x92\x1blb\xf2\x07\x96\xeaH:0\xd6\ 726 \xc1\x85\xd5\x98N5\xa5\xef?\xf5N\xd0\x8a\xdcA\xc2\xf7qw\x84\xdb\xfa\xb5\xcd\ 727 \xd4\xda;\xc9\x1a\xc8\xb6\xcd<\xb5\xa0\x85\x1e\xeb\xbc\xbc7b!\xf6\xdeHl\x1c\ 728 \x94\x073\xec<*\xf7\xbe\xf7\x99\x9d\xb21~\xe7.\xf5\x1f\x1c\xd3\xbdVlL\xc2\ 729 \xcf\xf8ye\xd0\x00\x90\x0etH \x84\x80B\xaa\x8a\x88\x85\xc4(U\x9d$\xfeR;\xc5J\ 730 \xa6\x01\xbbt9\xceR\xc8\x81e_$\x98\xb9\x9c\xa9\x8d,y\xa9t\xc8\xcf\x152\xe0x\ 731 \xe9$\xf5\x07\x95\x0cD\x95t:\xb1\x92\xae\x9cI\xa8~\x84\x1f\xe0\xa3ec""" 732
733 -def get_icon(wx=None):
734 735 paths = gmPaths(app_name = u'gnumed', wx = wx) 736 737 candidates = [ 738 os.path.join(paths.system_app_data_dir, 'bitmaps', 'gm_icon-serpent_and_gnu.png'), 739 os.path.join(paths.local_base_dir, 'bitmaps', 'gm_icon-serpent_and_gnu.png'), 740 os.path.join(paths.system_app_data_dir, 'bitmaps', 'serpent.png'), 741 os.path.join(paths.local_base_dir, 'bitmaps', 'serpent.png') 742 ] 743 744 found_as = None 745 for candidate in candidates: 746 try: 747 open(candidate, 'r').close() 748 found_as = candidate 749 break 750 except IOError: 751 _log.debug('icon not found in [%s]', candidate) 752 753 if found_as is None: 754 _log.warning('no icon file found, falling back to builtin (ugly) icon') 755 icon_bmp_data = wx.BitmapFromXPMData(cPickle.loads(zlib.decompress(__icon_serpent))) 756 icon.CopyFromBitmap(icon_bmp_data) 757 else: 758 _log.debug('icon found in [%s]', found_as) 759 icon = wx.EmptyIcon() 760 try: 761 icon.LoadFile(found_as, wx.BITMAP_TYPE_ANY) #_PNG 762 except AttributeError: 763 _log.exception(u"this platform doesn't support wx.Icon().LoadFile()") 764 765 return icon
766 #=========================================================================== 767 # main 768 #--------------------------------------------------------------------------- 769 if __name__ == '__main__': 770 771 if len(sys.argv) < 2: 772 sys.exit() 773 774 if sys.argv[1] != 'test': 775 sys.exit() 776 777 #-----------------------------------------------------------------------
778 - def test_input2decimal():
779 780 tests = [ 781 [None, False], 782 783 ['', False], 784 [' 0 ', True, 0], 785 786 [0, True, 0], 787 [0.0, True, 0], 788 [.0, True, 0], 789 ['0', True, 0], 790 ['0.0', True, 0], 791 ['0,0', True, 0], 792 ['00.0', True, 0], 793 ['.0', True, 0], 794 [',0', True, 0], 795 796 [0.1, True, decimal.Decimal('0.1')], 797 [.01, True, decimal.Decimal('0.01')], 798 ['0.1', True, decimal.Decimal('0.1')], 799 ['0,1', True, decimal.Decimal('0.1')], 800 ['00.1', True, decimal.Decimal('0.1')], 801 ['.1', True, decimal.Decimal('0.1')], 802 [',1', True, decimal.Decimal('0.1')], 803 804 [1, True, 1], 805 [1.0, True, 1], 806 ['1', True, 1], 807 ['1.', True, 1], 808 ['1,', True, 1], 809 ['1.0', True, 1], 810 ['1,0', True, 1], 811 ['01.0', True, 1], 812 ['01,0', True, 1], 813 [' 01, ', True, 1], 814 815 [decimal.Decimal('1.1'), True, decimal.Decimal('1.1')] 816 ] 817 for test in tests: 818 conversion_worked, result = input2decimal(initial = test[0]) 819 820 expected2work = test[1] 821 822 if conversion_worked: 823 if expected2work: 824 if result == test[2]: 825 continue 826 else: 827 print "ERROR (conversion result wrong): >%s<, expected >%s<, got >%s<" % (test[0], test[2], result) 828 else: 829 print "ERROR (conversion worked but was expected to fail): >%s<, got >%s<" % (test[0], result) 830 else: 831 if not expected2work: 832 continue 833 else: 834 print "ERROR (conversion failed but was expected to work): >%s<, expected >%s<" % (test[0], test[2])
835 #-----------------------------------------------------------------------
836 - def test_coalesce():
837 838 import datetime as dt 839 print coalesce(initial = dt.datetime.now(), template_initial = u'-- %s --', function_initial = ('strftime', u'%Y-%m-%d')) 840 841 print 'testing coalesce()' 842 print "------------------" 843 tests = [ 844 [None, 'something other than <None>', None, None, 'something other than <None>'], 845 ['Captain', 'Mr.', '%s.'[:4], 'Mr.', 'Capt.'], 846 ['value to test', 'test 3 failed', 'template with "%s" included', None, 'template with "value to test" included'], 847 ['value to test', 'test 4 failed', 'template with value not included', None, 'template with value not included'], 848 [None, 'initial value was None', 'template_initial: %s', None, 'initial value was None'], 849 [None, 'initial value was None', 'template_initial: %%(abc)s', None, 'initial value was None'] 850 ] 851 passed = True 852 for test in tests: 853 result = coalesce ( 854 initial = test[0], 855 instead = test[1], 856 template_initial = test[2], 857 template_instead = test[3] 858 ) 859 if result != test[4]: 860 print "ERROR" 861 print "coalesce: (%s, %s, %s, %s)" % (test[0], test[1], test[2], test[3]) 862 print "expected:", test[4] 863 print "received:", result 864 passed = False 865 866 if passed: 867 print "passed" 868 else: 869 print "failed" 870 return passed
871 #-----------------------------------------------------------------------
872 - def test_capitalize():
873 print 'testing capitalize() ...' 874 success = True 875 pairs = [ 876 # [original, expected result, CAPS mode] 877 [u'Boot', u'Boot', CAPS_FIRST_ONLY], 878 [u'boot', u'Boot', CAPS_FIRST_ONLY], 879 [u'booT', u'Boot', CAPS_FIRST_ONLY], 880 [u'BoOt', u'Boot', CAPS_FIRST_ONLY], 881 [u'boots-Schau', u'Boots-Schau', CAPS_WORDS], 882 [u'boots-sChau', u'Boots-Schau', CAPS_WORDS], 883 [u'boot camp', u'Boot Camp', CAPS_WORDS], 884 [u'fahrner-Kampe', u'Fahrner-Kampe', CAPS_NAMES], 885 [u'häkkönen', u'Häkkönen', CAPS_NAMES], 886 [u'McBurney', u'McBurney', CAPS_NAMES], 887 [u'mcBurney', u'McBurney', CAPS_NAMES], 888 [u'blumberg', u'Blumberg', CAPS_NAMES], 889 [u'roVsing', u'RoVsing', CAPS_NAMES], 890 [u'Özdemir', u'Özdemir', CAPS_NAMES], 891 [u'özdemir', u'Özdemir', CAPS_NAMES], 892 ] 893 for pair in pairs: 894 result = capitalize(pair[0], pair[2]) 895 if result != pair[1]: 896 success = False 897 print 'ERROR (caps mode %s): "%s" -> "%s", expected "%s"' % (pair[2], pair[0], result, pair[1]) 898 899 if success: 900 print "... SUCCESS" 901 902 return success
903 #-----------------------------------------------------------------------
904 - def test_import_module():
905 print "testing import_module_from_directory()" 906 path = sys.argv[1] 907 name = sys.argv[2] 908 try: 909 mod = import_module_from_directory(module_path = path, module_name = name) 910 except: 911 print "module import failed, see log" 912 return False 913 914 print "module import succeeded", mod 915 print dir(mod) 916 return True
917 #-----------------------------------------------------------------------
918 - def test_mkdir():
919 print "testing mkdir()" 920 mkdir(sys.argv[1])
921 #-----------------------------------------------------------------------
922 - def test_gmPaths():
923 print "testing gmPaths()" 924 print "-----------------" 925 paths = gmPaths(wx=None, app_name='gnumed') 926 print "user config dir:", paths.user_config_dir 927 print "system config dir:", paths.system_config_dir 928 print "local base dir:", paths.local_base_dir 929 print "system app data dir:", paths.system_app_data_dir 930 print "working directory :", paths.working_dir 931 print "temp directory :", paths.tmp_dir
932 #-----------------------------------------------------------------------
933 - def test_none_if():
934 print "testing none_if()" 935 print "-----------------" 936 tests = [ 937 [None, None, None], 938 ['a', 'a', None], 939 ['a', 'b', 'a'], 940 ['a', None, 'a'], 941 [None, 'a', None], 942 [1, 1, None], 943 [1, 2, 1], 944 [1, None, 1], 945 [None, 1, None] 946 ] 947 948 for test in tests: 949 if none_if(value = test[0], none_equivalent = test[1]) != test[2]: 950 print 'ERROR: none_if(%s) returned [%s], expected [%s]' % (test[0], none_if(test[0], test[1]), test[2]) 951 952 return True
953 #-----------------------------------------------------------------------
954 - def test_bool2str():
955 tests = [ 956 [True, 'Yes', 'Yes', 'Yes'], 957 [False, 'OK', 'not OK', 'not OK'] 958 ] 959 for test in tests: 960 if bool2str(test[0], test[1], test[2]) != test[3]: 961 print 'ERROR: bool2str(%s, %s, %s) returned [%s], expected [%s]' % (test[0], test[1], test[2], bool2str(test[0], test[1], test[2]), test[3]) 962 963 return True
964 #-----------------------------------------------------------------------
965 - def test_bool2subst():
966 967 print bool2subst(True, 'True', 'False', 'is None') 968 print bool2subst(False, 'True', 'False', 'is None') 969 print bool2subst(None, 'True', 'False', 'is None')
970 #-----------------------------------------------------------------------
971 - def test_get_unique_filename():
972 print get_unique_filename() 973 print get_unique_filename(prefix='test-') 974 print get_unique_filename(suffix='tst') 975 print get_unique_filename(prefix='test-', suffix='tst') 976 print get_unique_filename(tmp_dir='/home/ncq/Archiv/')
977 #-----------------------------------------------------------------------
978 - def test_size2str():
979 print "testing size2str()" 980 print "------------------" 981 tests = [0, 1, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000, 1000000000000, 10000000000000] 982 for test in tests: 983 print size2str(test)
984 #-----------------------------------------------------------------------
985 - def test_unwrap():
986 987 test = """ 988 second line\n 989 3rd starts with tab \n 990 4th with a space \n 991 992 6th 993 994 """ 995 print unwrap(text = test, max_length = 25)
996 #-----------------------------------------------------------------------
997 - def test_wrap():
998 test = 'line 1\nline 2\nline 3' 999 1000 print "wrap 5-6-7 initial 0, subsequent 0" 1001 print wrap(test, 5) 1002 print 1003 print wrap(test, 6) 1004 print 1005 print wrap(test, 7) 1006 print "-------" 1007 raw_input() 1008 print "wrap 5 initial 1-1-3, subsequent 1-3-1" 1009 print wrap(test, 5, u' ', u' ') 1010 print 1011 print wrap(test, 5, u' ', u' ') 1012 print 1013 print wrap(test, 5, u' ', u' ') 1014 print "-------" 1015 raw_input() 1016 print "wrap 6 initial 1-1-3, subsequent 1-3-1" 1017 print wrap(test, 6, u' ', u' ') 1018 print 1019 print wrap(test, 6, u' ', u' ') 1020 print 1021 print wrap(test, 6, u' ', u' ') 1022 print "-------" 1023 raw_input() 1024 print "wrap 7 initial 1-1-3, subsequent 1-3-1" 1025 print wrap(test, 7, u' ', u' ') 1026 print 1027 print wrap(test, 7, u' ', u' ') 1028 print 1029 print wrap(test, 7, u' ', u' ')
1030 #-----------------------------------------------------------------------
1031 - def test_md5():
1032 print '%s: %s' % (sys.argv[2], file2md5(sys.argv[2]))
1033 #----------------------------------------------------------------------- 1034 #test_coalesce() 1035 #test_capitalize() 1036 #test_import_module() 1037 #test_mkdir() 1038 #test_gmPaths() 1039 #test_none_if() 1040 #test_bool2str() 1041 #test_bool2subst() 1042 #test_get_unique_filename() 1043 #test_size2str() 1044 #test_wrap() 1045 test_input2decimal() 1046 #test_unwrap() 1047 #test_md5() 1048 1049 #=========================================================================== 1050