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