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