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