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