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 #---------------------------------------------------------------------------
440 -def fname_stem(filename):
441 return os.path.splitext(os.path.basename(filename))[0]
442 443 #---------------------------------------------------------------------------
444 -def get_unique_filename(prefix=None, suffix=None, tmp_dir=None):
445 """This introduces a race condition between the file.close() and 446 actually using the filename. 447 448 The file will NOT exist after calling this function. 449 """ 450 if tmp_dir is not None: 451 if ( 452 not os.access(tmp_dir, os.F_OK) 453 or 454 not os.access(tmp_dir, os.X_OK | os.W_OK) 455 ): 456 _log.warning('cannot find temporary dir [%s], using system default', tmp_dir) 457 tmp_dir = None 458 459 kwargs = {'dir': tmp_dir} 460 461 if prefix is None: 462 kwargs['prefix'] = 'gnumed-' 463 else: 464 kwargs['prefix'] = prefix 465 466 if suffix in [None, u'']: 467 kwargs['suffix'] = '.tmp' 468 else: 469 if not suffix.startswith('.'): 470 suffix = '.' + suffix 471 kwargs['suffix'] = suffix 472 473 f = tempfile.NamedTemporaryFile(**kwargs) 474 filename = f.name 475 f.close() 476 477 return filename
478 #===========================================================================
479 -def import_module_from_directory(module_path=None, module_name=None, always_remove_path=False):
480 """Import a module from any location.""" 481 482 remove_path = always_remove_path or False 483 if module_path not in sys.path: 484 _log.info('appending to sys.path: [%s]' % module_path) 485 sys.path.append(module_path) 486 remove_path = True 487 488 _log.debug('will remove import path: %s', remove_path) 489 490 if module_name.endswith('.py'): 491 module_name = module_name[:-3] 492 493 try: 494 module = __import__(module_name) 495 except StandardError: 496 _log.exception('cannot __import__() module [%s] from [%s]' % (module_name, module_path)) 497 while module_path in sys.path: 498 sys.path.remove(module_path) 499 raise 500 501 _log.info('imported module [%s] as [%s]' % (module_name, module)) 502 if remove_path: 503 while module_path in sys.path: 504 sys.path.remove(module_path) 505 506 return module
507 #=========================================================================== 508 # text related tools 509 #--------------------------------------------------------------------------- 510 _kB = 1024 511 _MB = 1024 * _kB 512 _GB = 1024 * _MB 513 _TB = 1024 * _GB 514 _PB = 1024 * _TB 515 #---------------------------------------------------------------------------
516 -def size2str(size=0, template=u'%s'):
517 if size == 1: 518 return template % _('1 Byte') 519 if size < 10 * _kB: 520 return template % _('%s Bytes') % size 521 if size < _MB: 522 return template % u'%.1f kB' % (float(size) / _kB) 523 if size < _GB: 524 return template % u'%.1f MB' % (float(size) / _MB) 525 if size < _TB: 526 return template % u'%.1f GB' % (float(size) / _GB) 527 if size < _PB: 528 return template % u'%.1f TB' % (float(size) / _TB) 529 return template % u'%.1f PB' % (float(size) / _PB)
530 #---------------------------------------------------------------------------
531 -def bool2subst(boolean=None, true_return=True, false_return=False, none_return=None):
532 if boolean is None: 533 return none_return 534 if boolean: 535 return true_return 536 if not boolean: 537 return false_return 538 raise ValueError('bool2subst(): <boolean> arg must be either of True, False, None')
539 #---------------------------------------------------------------------------
540 -def bool2str(boolean=None, true_str='True', false_str='False'):
541 return bool2subst ( 542 boolean = bool(boolean), 543 true_return = true_str, 544 false_return = false_str 545 )
546 #---------------------------------------------------------------------------
547 -def none_if(value=None, none_equivalent=None, strip_string=False):
548 """Modelled after the SQL NULLIF function.""" 549 if value is None: 550 return None 551 if strip_string: 552 stripped = value.strip() 553 else: 554 stripped = value 555 if stripped == none_equivalent: 556 return None 557 return value
558 #---------------------------------------------------------------------------
559 -def coalesce(initial=None, instead=None, template_initial=None, template_instead=None, none_equivalents=None, function_initial=None):
560 """Modelled after the SQL coalesce function. 561 562 To be used to simplify constructs like: 563 564 if initial is None (or in none_equivalents): 565 real_value = (template_instead % instead) or instead 566 else: 567 real_value = (template_initial % initial) or initial 568 print real_value 569 570 @param initial: the value to be tested for <None> 571 @type initial: any Python type, must have a __str__ method if template_initial is not None 572 @param instead: the value to be returned if <initial> is None 573 @type instead: any Python type, must have a __str__ method if template_instead is not None 574 @param template_initial: if <initial> is returned replace the value into this template, must contain one <%s> 575 @type template_initial: string or None 576 @param template_instead: if <instead> is returned replace the value into this template, must contain one <%s> 577 @type template_instead: string or None 578 579 example: 580 function_initial = ('strftime', '%Y-%m-%d') 581 582 Ideas: 583 - list of insteads: initial, [instead, template], [instead, template], [instead, template], template_initial, ... 584 """ 585 if none_equivalents is None: 586 none_equivalents = [None] 587 588 if initial in none_equivalents: 589 590 if template_instead is None: 591 return instead 592 593 return template_instead % instead 594 595 if function_initial is not None: 596 funcname, args = function_initial 597 func = getattr(initial, funcname) 598 initial = func(args) 599 600 if template_initial is None: 601 return initial 602 603 try: 604 return template_initial % initial 605 except TypeError: 606 return template_initial
607 #---------------------------------------------------------------------------
608 -def __cap_name(match_obj=None):
609 val = match_obj.group(0).lower() 610 if val in ['von', 'van', 'de', 'la', 'l', 'der', 'den']: # FIXME: this needs to expand, configurable ? 611 return val 612 buf = list(val) 613 buf[0] = buf[0].upper() 614 for part in ['mac', 'mc', 'de', 'la']: 615 if len(val) > len(part) and val[:len(part)] == part: 616 buf[len(part)] = buf[len(part)].upper() 617 return ''.join(buf)
618 #---------------------------------------------------------------------------
619 -def capitalize(text=None, mode=CAPS_NAMES):
620 """Capitalize the first character but leave the rest alone. 621 622 Note that we must be careful about the locale, this may 623 have issues ! However, for UTF strings it should just work. 624 """ 625 if (mode is None) or (mode == CAPS_NONE): 626 return text 627 628 if len(text) == 0: 629 return text 630 631 if mode == CAPS_FIRST: 632 if len(text) == 1: 633 return text[0].upper() 634 return text[0].upper() + text[1:] 635 636 if mode == CAPS_ALLCAPS: 637 return text.upper() 638 639 if mode == CAPS_FIRST_ONLY: 640 if len(text) == 1: 641 return text[0].upper() 642 return text[0].upper() + text[1:].lower() 643 644 if mode == CAPS_WORDS: 645 return regex.sub(ur'(\w)(\w+)', lambda x: x.group(1).upper() + x.group(2).lower(), text) 646 647 if mode == CAPS_NAMES: 648 #return regex.sub(r'\w+', __cap_name, text) 649 return capitalize(text=text, mode=CAPS_FIRST) # until fixed 650 651 print "ERROR: invalid capitalization mode: [%s], leaving input as is" % mode 652 return text
653 #---------------------------------------------------------------------------
654 -def input2decimal(initial=None):
655 656 if isinstance(initial, decimal.Decimal): 657 return True, initial 658 659 val = initial 660 661 # float ? -> to string first 662 if type(val) == type(float(1.4)): 663 val = str(val) 664 665 # string ? -> "," to "." 666 if isinstance(val, basestring): 667 val = val.replace(',', '.', 1) 668 val = val.strip() 669 670 try: 671 d = decimal.Decimal(val) 672 return True, d 673 except (TypeError, decimal.InvalidOperation): 674 return False, val
675 #---------------------------------------------------------------------------
676 -def input2int(initial=None, minval=None, maxval=None):
677 678 val = initial 679 680 # string ? -> "," to "." 681 if isinstance(val, basestring): 682 val = val.replace(',', '.', 1) 683 val = val.strip() 684 685 try: 686 int_val = int(val) 687 except (TypeError, ValueError): 688 _log.exception('int(%s) failed', val) 689 return False, val 690 691 if minval is not None: 692 if int_val < minval: 693 _log.debug('%s < min (%s)', val, minval) 694 return False, val 695 if maxval is not None: 696 if int_val > maxval: 697 _log.debug('%s > max (%s)', val, maxval) 698 return False, val 699 700 return True, int_val
701 #---------------------------------------------------------------------------
702 -def strip_leading_empty_lines(lines=None, text=None, eol=u'\n', return_list=True):
703 if lines is None: 704 lines = text.split(eol) 705 706 while True: 707 if lines[0].strip(eol).strip() != u'': 708 break 709 lines = lines[1:] 710 711 if return_list: 712 return lines 713 714 return eol.join(lines)
715 #---------------------------------------------------------------------------
716 -def strip_trailing_empty_lines(lines=None, text=None, eol=u'\n', return_list=True):
717 if lines is None: 718 lines = text.split(eol) 719 720 while True: 721 if lines[-1].strip(eol).strip() != u'': 722 break 723 lines = lines[:-1] 724 725 if return_list: 726 return lines 727 728 return eol.join(lines)
729 #---------------------------------------------------------------------------
730 -def wrap(text=None, width=None, initial_indent=u'', subsequent_indent=u'', eol=u'\n'):
731 """A word-wrap function that preserves existing line breaks 732 and most spaces in the text. Expects that existing line 733 breaks are posix newlines (\n). 734 """ 735 if width is None: 736 return text 737 wrapped = initial_indent + reduce ( 738 lambda line, word, width=width: '%s%s%s' % ( 739 line, 740 ' \n'[(len(line) - line.rfind('\n') - 1 + len(word.split('\n',1)[0]) >= width)], 741 word 742 ), 743 text.split(' ') 744 ) 745 746 if subsequent_indent != u'': 747 wrapped = (u'\n%s' % subsequent_indent).join(wrapped.split('\n')) 748 749 if eol != u'\n': 750 wrapped = wrapped.replace('\n', eol) 751 752 return wrapped
753 #---------------------------------------------------------------------------
754 -def unwrap(text=None, max_length=None, strip_whitespace=True, remove_empty_lines=True, line_separator = u' // '):
755 756 text = text.replace(u'\r', u'') 757 lines = text.split(u'\n') 758 text = u'' 759 for line in lines: 760 761 if strip_whitespace: 762 line = line.strip().strip(u'\t').strip() 763 764 if remove_empty_lines: 765 if line == u'': 766 continue 767 768 text += (u'%s%s' % (line, line_separator)) 769 770 text = text.rstrip(line_separator) 771 772 if max_length is not None: 773 text = text[:max_length] 774 775 text = text.rstrip(line_separator) 776 777 return text
778 #---------------------------------------------------------------------------
779 -def xml_escape_string(text=None):
780 """check for special XML characters and transform them""" 781 return xml_tools.escape(text)
782 #---------------------------------------------------------------------------
783 -def tex_escape_string(text=None, replace_known_unicode=True):
784 """check for special TeX characters and transform them""" 785 786 text = text.replace(u'\\', u'\\textbackslash') 787 text = text.replace(u'^', u'\\textasciicircum') 788 text = text.replace('~','\\textasciitilde') 789 790 text = text.replace(u'{', u'\\{') 791 text = text.replace(u'}', u'\\}') 792 text = text.replace(u'%', u'\\%') 793 text = text.replace(u'&', u'\\&') 794 text = text.replace(u'#', u'\\#') 795 text = text.replace(u'$', u'\\$') 796 text = text.replace(u'_', u'\\_') 797 798 if replace_known_unicode: 799 # this should NOT be replaced for Xe(La)Tex 800 text = text.replace(u_euro, u'\\EUR') 801 802 return text
803 #---------------------------------------------------------------------------
804 -def xetex_escape_string(text=None):
805 # a web search did not reveal anything else for Xe(La)Tex 806 # as opposed to LaTeX, except true unicode chars 807 return tex_escape_string(text = text, replace_known_unicode = False)
808 #---------------------------------------------------------------------------
809 -def prompted_input(prompt=None, default=None):
810 """Obtains entry from standard input. 811 812 prompt: Prompt text to display in standard output 813 default: Default value (for user to press enter only) 814 CTRL-C: aborts and returns None 815 """ 816 if prompt is None: 817 msg = u'(CTRL-C aborts)' 818 else: 819 msg = u'%s (CTRL-C aborts)' % prompt 820 821 if default is None: 822 msg = msg + u': ' 823 else: 824 msg = u'%s [%s]: ' % (msg, default) 825 826 try: 827 usr_input = raw_input(msg) 828 except KeyboardInterrupt: 829 return None 830 831 if usr_input == '': 832 return default 833 834 return usr_input
835 836 #=========================================================================== 837 # image handling tools 838 #--------------------------------------------------------------------------- 839 # builtin (ugly but tried and true) fallback icon 840 __icon_serpent = \ 841 """x\xdae\x8f\xb1\x0e\x83 \x10\x86w\x9f\xe2\x92\x1blb\xf2\x07\x96\xeaH:0\xd6\ 842 \xc1\x85\xd5\x98N5\xa5\xef?\xf5N\xd0\x8a\xdcA\xc2\xf7qw\x84\xdb\xfa\xb5\xcd\ 843 \xd4\xda;\xc9\x1a\xc8\xb6\xcd<\xb5\xa0\x85\x1e\xeb\xbc\xbc7b!\xf6\xdeHl\x1c\ 844 \x94\x073\xec<*\xf7\xbe\xf7\x99\x9d\xb21~\xe7.\xf5\x1f\x1c\xd3\xbdVlL\xc2\ 845 \xcf\xf8ye\xd0\x00\x90\x0etH \x84\x80B\xaa\x8a\x88\x85\xc4(U\x9d$\xfeR;\xc5J\ 846 \xa6\x01\xbbt9\xceR\xc8\x81e_$\x98\xb9\x9c\xa9\x8d,y\xa9t\xc8\xcf\x152\xe0x\ 847 \xe9$\xf5\x07\x95\x0cD\x95t:\xb1\x92\xae\x9cI\xa8~\x84\x1f\xe0\xa3ec""" 848
849 -def get_icon(wx=None):
850 851 paths = gmPaths(app_name = u'gnumed', wx = wx) 852 853 candidates = [ 854 os.path.join(paths.system_app_data_dir, 'bitmaps', 'gm_icon-serpent_and_gnu.png'), 855 os.path.join(paths.local_base_dir, 'bitmaps', 'gm_icon-serpent_and_gnu.png'), 856 os.path.join(paths.system_app_data_dir, 'bitmaps', 'serpent.png'), 857 os.path.join(paths.local_base_dir, 'bitmaps', 'serpent.png') 858 ] 859 860 found_as = None 861 for candidate in candidates: 862 try: 863 open(candidate, 'r').close() 864 found_as = candidate 865 break 866 except IOError: 867 _log.debug('icon not found in [%s]', candidate) 868 869 if found_as is None: 870 _log.warning('no icon file found, falling back to builtin (ugly) icon') 871 icon_bmp_data = wx.BitmapFromXPMData(cPickle.loads(zlib.decompress(__icon_serpent))) 872 icon.CopyFromBitmap(icon_bmp_data) 873 else: 874 _log.debug('icon found in [%s]', found_as) 875 icon = wx.EmptyIcon() 876 try: 877 icon.LoadFile(found_as, wx.BITMAP_TYPE_ANY) #_PNG 878 except AttributeError: 879 _log.exception(u"this platform doesn't support wx.Icon().LoadFile()") 880 881 return icon
882 #=========================================================================== 883 # main 884 #--------------------------------------------------------------------------- 885 if __name__ == '__main__': 886 887 if len(sys.argv) < 2: 888 sys.exit() 889 890 if sys.argv[1] != 'test': 891 sys.exit() 892 893 #-----------------------------------------------------------------------
894 - def test_input2decimal():
895 896 tests = [ 897 [None, False], 898 899 ['', False], 900 [' 0 ', True, 0], 901 902 [0, True, 0], 903 [0.0, True, 0], 904 [.0, True, 0], 905 ['0', True, 0], 906 ['0.0', True, 0], 907 ['0,0', True, 0], 908 ['00.0', True, 0], 909 ['.0', True, 0], 910 [',0', True, 0], 911 912 [0.1, True, decimal.Decimal('0.1')], 913 [.01, True, decimal.Decimal('0.01')], 914 ['0.1', True, decimal.Decimal('0.1')], 915 ['0,1', True, decimal.Decimal('0.1')], 916 ['00.1', True, decimal.Decimal('0.1')], 917 ['.1', True, decimal.Decimal('0.1')], 918 [',1', True, decimal.Decimal('0.1')], 919 920 [1, True, 1], 921 [1.0, True, 1], 922 ['1', True, 1], 923 ['1.', True, 1], 924 ['1,', True, 1], 925 ['1.0', True, 1], 926 ['1,0', True, 1], 927 ['01.0', True, 1], 928 ['01,0', True, 1], 929 [' 01, ', True, 1], 930 931 [decimal.Decimal('1.1'), True, decimal.Decimal('1.1')] 932 ] 933 for test in tests: 934 conversion_worked, result = input2decimal(initial = test[0]) 935 936 expected2work = test[1] 937 938 if conversion_worked: 939 if expected2work: 940 if result == test[2]: 941 continue 942 else: 943 print "ERROR (conversion result wrong): >%s<, expected >%s<, got >%s<" % (test[0], test[2], result) 944 else: 945 print "ERROR (conversion worked but was expected to fail): >%s<, got >%s<" % (test[0], result) 946 else: 947 if not expected2work: 948 continue 949 else: 950 print "ERROR (conversion failed but was expected to work): >%s<, expected >%s<" % (test[0], test[2])
951 #-----------------------------------------------------------------------
952 - def test_input2int():
953 print input2int(0) 954 print input2int('0') 955 print input2int(u'0', 0, 0)
956 #-----------------------------------------------------------------------
957 - def test_coalesce():
958 959 import datetime as dt 960 print coalesce(initial = dt.datetime.now(), template_initial = u'-- %s --', function_initial = ('strftime', u'%Y-%m-%d')) 961 962 print 'testing coalesce()' 963 print "------------------" 964 tests = [ 965 [None, 'something other than <None>', None, None, 'something other than <None>'], 966 ['Captain', 'Mr.', '%s.'[:4], 'Mr.', 'Capt.'], 967 ['value to test', 'test 3 failed', 'template with "%s" included', None, 'template with "value to test" included'], 968 ['value to test', 'test 4 failed', 'template with value not included', None, 'template with value not included'], 969 [None, 'initial value was None', 'template_initial: %s', None, 'initial value was None'], 970 [None, 'initial value was None', 'template_initial: %%(abc)s', None, 'initial value was None'] 971 ] 972 passed = True 973 for test in tests: 974 result = coalesce ( 975 initial = test[0], 976 instead = test[1], 977 template_initial = test[2], 978 template_instead = test[3] 979 ) 980 if result != test[4]: 981 print "ERROR" 982 print "coalesce: (%s, %s, %s, %s)" % (test[0], test[1], test[2], test[3]) 983 print "expected:", test[4] 984 print "received:", result 985 passed = False 986 987 if passed: 988 print "passed" 989 else: 990 print "failed" 991 return passed
992 #-----------------------------------------------------------------------
993 - def test_capitalize():
994 print 'testing capitalize() ...' 995 success = True 996 pairs = [ 997 # [original, expected result, CAPS mode] 998 [u'Boot', u'Boot', CAPS_FIRST_ONLY], 999 [u'boot', u'Boot', CAPS_FIRST_ONLY], 1000 [u'booT', u'Boot', CAPS_FIRST_ONLY], 1001 [u'BoOt', u'Boot', CAPS_FIRST_ONLY], 1002 [u'boots-Schau', u'Boots-Schau', CAPS_WORDS], 1003 [u'boots-sChau', u'Boots-Schau', CAPS_WORDS], 1004 [u'boot camp', u'Boot Camp', CAPS_WORDS], 1005 [u'fahrner-Kampe', u'Fahrner-Kampe', CAPS_NAMES], 1006 [u'häkkönen', u'Häkkönen', CAPS_NAMES], 1007 [u'McBurney', u'McBurney', CAPS_NAMES], 1008 [u'mcBurney', u'McBurney', CAPS_NAMES], 1009 [u'blumberg', u'Blumberg', CAPS_NAMES], 1010 [u'roVsing', u'RoVsing', CAPS_NAMES], 1011 [u'Özdemir', u'Özdemir', CAPS_NAMES], 1012 [u'özdemir', u'Özdemir', CAPS_NAMES], 1013 ] 1014 for pair in pairs: 1015 result = capitalize(pair[0], pair[2]) 1016 if result != pair[1]: 1017 success = False 1018 print 'ERROR (caps mode %s): "%s" -> "%s", expected "%s"' % (pair[2], pair[0], result, pair[1]) 1019 1020 if success: 1021 print "... SUCCESS" 1022 1023 return success
1024 #-----------------------------------------------------------------------
1025 - def test_import_module():
1026 print "testing import_module_from_directory()" 1027 path = sys.argv[1] 1028 name = sys.argv[2] 1029 try: 1030 mod = import_module_from_directory(module_path = path, module_name = name) 1031 except: 1032 print "module import failed, see log" 1033 return False 1034 1035 print "module import succeeded", mod 1036 print dir(mod) 1037 return True
1038 #-----------------------------------------------------------------------
1039 - def test_mkdir():
1040 print "testing mkdir()" 1041 mkdir(sys.argv[1])
1042 #-----------------------------------------------------------------------
1043 - def test_gmPaths():
1044 print "testing gmPaths()" 1045 print "-----------------" 1046 paths = gmPaths(wx=None, app_name='gnumed') 1047 print "user config dir:", paths.user_config_dir 1048 print "system config dir:", paths.system_config_dir 1049 print "local base dir:", paths.local_base_dir 1050 print "system app data dir:", paths.system_app_data_dir 1051 print "working directory :", paths.working_dir 1052 print "temp directory :", paths.tmp_dir
1053 #-----------------------------------------------------------------------
1054 - def test_none_if():
1055 print "testing none_if()" 1056 print "-----------------" 1057 tests = [ 1058 [None, None, None], 1059 ['a', 'a', None], 1060 ['a', 'b', 'a'], 1061 ['a', None, 'a'], 1062 [None, 'a', None], 1063 [1, 1, None], 1064 [1, 2, 1], 1065 [1, None, 1], 1066 [None, 1, None] 1067 ] 1068 1069 for test in tests: 1070 if none_if(value = test[0], none_equivalent = test[1]) != test[2]: 1071 print 'ERROR: none_if(%s) returned [%s], expected [%s]' % (test[0], none_if(test[0], test[1]), test[2]) 1072 1073 return True
1074 #-----------------------------------------------------------------------
1075 - def test_bool2str():
1076 tests = [ 1077 [True, 'Yes', 'Yes', 'Yes'], 1078 [False, 'OK', 'not OK', 'not OK'] 1079 ] 1080 for test in tests: 1081 if bool2str(test[0], test[1], test[2]) != test[3]: 1082 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]) 1083 1084 return True
1085 #-----------------------------------------------------------------------
1086 - def test_bool2subst():
1087 1088 print bool2subst(True, 'True', 'False', 'is None') 1089 print bool2subst(False, 'True', 'False', 'is None') 1090 print bool2subst(None, 'True', 'False', 'is None')
1091 #-----------------------------------------------------------------------
1092 - def test_get_unique_filename():
1093 print get_unique_filename() 1094 print get_unique_filename(prefix='test-') 1095 print get_unique_filename(suffix='tst') 1096 print get_unique_filename(prefix='test-', suffix='tst') 1097 print get_unique_filename(tmp_dir='/home/ncq/Archiv/')
1098 #-----------------------------------------------------------------------
1099 - def test_size2str():
1100 print "testing size2str()" 1101 print "------------------" 1102 tests = [0, 1, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000, 1000000000000, 10000000000000] 1103 for test in tests: 1104 print size2str(test)
1105 #-----------------------------------------------------------------------
1106 - def test_unwrap():
1107 1108 test = """ 1109 second line\n 1110 3rd starts with tab \n 1111 4th with a space \n 1112 1113 6th 1114 1115 """ 1116 print unwrap(text = test, max_length = 25)
1117 #-----------------------------------------------------------------------
1118 - def test_wrap():
1119 test = 'line 1\nline 2\nline 3' 1120 1121 print "wrap 5-6-7 initial 0, subsequent 0" 1122 print wrap(test, 5) 1123 print 1124 print wrap(test, 6) 1125 print 1126 print wrap(test, 7) 1127 print "-------" 1128 raw_input() 1129 print "wrap 5 initial 1-1-3, subsequent 1-3-1" 1130 print wrap(test, 5, u' ', u' ') 1131 print 1132 print wrap(test, 5, u' ', u' ') 1133 print 1134 print wrap(test, 5, u' ', u' ') 1135 print "-------" 1136 raw_input() 1137 print "wrap 6 initial 1-1-3, subsequent 1-3-1" 1138 print wrap(test, 6, u' ', u' ') 1139 print 1140 print wrap(test, 6, u' ', u' ') 1141 print 1142 print wrap(test, 6, u' ', u' ') 1143 print "-------" 1144 raw_input() 1145 print "wrap 7 initial 1-1-3, subsequent 1-3-1" 1146 print wrap(test, 7, u' ', u' ') 1147 print 1148 print wrap(test, 7, u' ', u' ') 1149 print 1150 print wrap(test, 7, u' ', u' ')
1151 #-----------------------------------------------------------------------
1152 - def test_md5():
1153 print '%s: %s' % (sys.argv[2], file2md5(sys.argv[2]))
1154 #-----------------------------------------------------------------------
1155 - def test_unicode():
1156 print u_link_symbol * 10
1157 #-----------------------------------------------------------------------
1158 - def test_xml_escape():
1159 print xml_escape_string(u'<') 1160 print xml_escape_string(u'>') 1161 print xml_escape_string(u'&')
1162 #-----------------------------------------------------------------------
1163 - def test_gpg_decrypt():
1164 fname = gpg_decrypt_file(filename = sys.argv[2], passphrase = sys.argv[3]) 1165 if fname is not None: 1166 print "successfully decrypted:", fname
1167 #-----------------------------------------------------------------------
1168 - def test_strip_trailing_empty_lines():
1169 tests = [ 1170 u'one line, no embedded line breaks ', 1171 u'one line\nwith embedded\nline\nbreaks\n ' 1172 ] 1173 for test in tests: 1174 print 'as list:' 1175 print strip_trailing_empty_lines(text = test, eol=u'\n', return_list = True) 1176 print 'as string:' 1177 print u'>>>%s<<<' % strip_trailing_empty_lines(text = test, eol=u'\n', return_list = False) 1178 tests = [ 1179 ['list', 'without', 'empty', 'trailing', 'lines'], 1180 ['list', 'with', 'empty', 'trailing', 'lines', '', ' ', ''] 1181 ] 1182 for test in tests: 1183 print 'as list:' 1184 print strip_trailing_empty_lines(lines = test, eol = u'\n', return_list = True) 1185 print 'as string:' 1186 print strip_trailing_empty_lines(lines = test, eol = u'\n', return_list = False)
1187 #-----------------------------------------------------------------------
1188 - def test_fname_stem():
1189 tests = [ 1190 r'abc.exe', 1191 r'\abc.exe', 1192 r'c:\abc.exe', 1193 r'c:\d\abc.exe', 1194 r'/home/ncq/tmp.txt', 1195 r'~/tmp.txt', 1196 r'./tmp.txt', 1197 r'./.././tmp.txt', 1198 r'tmp.txt' 1199 ] 1200 for t in tests: 1201 print "[%s] -> [%s]" % (t, fname_stem(t))
1202 #----------------------------------------------------------------------- 1203 #test_coalesce() 1204 #test_capitalize() 1205 #test_import_module() 1206 #test_mkdir() 1207 #test_gmPaths() 1208 #test_none_if() 1209 #test_bool2str() 1210 #test_bool2subst() 1211 #test_get_unique_filename() 1212 #test_size2str() 1213 #test_wrap() 1214 #test_input2decimal() 1215 #test_input2int() 1216 #test_unwrap() 1217 #test_md5() 1218 #test_unicode() 1219 #test_xml_escape() 1220 #test_gpg_decrypt() 1221 #test_strip_trailing_empty_lines() 1222 test_fname_stem() 1223 1224 #=========================================================================== 1225