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