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 strip_empty_lines(lines=None, text=None, eol=u'\n', return_list=True):
737 return strip_trailing_empty_lines ( 738 lines = strip_leading_empty_lines(lines = lines, text = text, eol = eol, return_list = True), 739 text = None, 740 eol = eol, 741 return_list = return_list 742 )
743 #---------------------------------------------------------------------------
744 -def wrap(text=None, width=None, initial_indent=u'', subsequent_indent=u'', eol=u'\n'):
745 """A word-wrap function that preserves existing line breaks 746 and most spaces in the text. Expects that existing line 747 breaks are posix newlines (\n). 748 """ 749 if width is None: 750 return text 751 wrapped = initial_indent + reduce ( 752 lambda line, word, width=width: '%s%s%s' % ( 753 line, 754 ' \n'[(len(line) - line.rfind('\n') - 1 + len(word.split('\n',1)[0]) >= width)], 755 word 756 ), 757 text.split(' ') 758 ) 759 760 if subsequent_indent != u'': 761 wrapped = (u'\n%s' % subsequent_indent).join(wrapped.split('\n')) 762 763 if eol != u'\n': 764 wrapped = wrapped.replace('\n', eol) 765 766 return wrapped
767 #---------------------------------------------------------------------------
768 -def unwrap(text=None, max_length=None, strip_whitespace=True, remove_empty_lines=True, line_separator = u' // '):
769 770 text = text.replace(u'\r', u'') 771 lines = text.split(u'\n') 772 text = u'' 773 for line in lines: 774 775 if strip_whitespace: 776 line = line.strip().strip(u'\t').strip() 777 778 if remove_empty_lines: 779 if line == u'': 780 continue 781 782 text += (u'%s%s' % (line, line_separator)) 783 784 text = text.rstrip(line_separator) 785 786 if max_length is not None: 787 text = text[:max_length] 788 789 text = text.rstrip(line_separator) 790 791 return text
792 #---------------------------------------------------------------------------
793 -def xml_escape_string(text=None):
794 """check for special XML characters and transform them""" 795 return xml_tools.escape(text)
796 #---------------------------------------------------------------------------
797 -def tex_escape_string(text=None, replace_known_unicode=True):
798 """check for special TeX characters and transform them""" 799 800 text = text.replace(u'\\', u'\\textbackslash') 801 text = text.replace(u'^', u'\\textasciicircum') 802 text = text.replace(u'~', u'\\textasciitilde') 803 804 text = text.replace(u'{', u'\\{') 805 text = text.replace(u'}', u'\\}') 806 text = text.replace(u'%', u'\\%') 807 text = text.replace(u'&', u'\\&') 808 text = text.replace(u'#', u'\\#') 809 text = text.replace(u'$', u'\\$') 810 text = text.replace(u'_', u'\\_') 811 812 if replace_known_unicode: 813 # this should NOT be replaced for Xe(La)Tex 814 text = text.replace(u_euro, u'\\EUR') 815 816 return text
817 #---------------------------------------------------------------------------
818 -def xetex_escape_string(text=None):
819 # a web search did not reveal anything else for Xe(La)Tex 820 # as opposed to LaTeX, except true unicode chars 821 return tex_escape_string(text = text, replace_known_unicode = False)
822 #---------------------------------------------------------------------------
823 -def prompted_input(prompt=None, default=None):
824 """Obtains entry from standard input. 825 826 prompt: Prompt text to display in standard output 827 default: Default value (for user to press enter only) 828 CTRL-C: aborts and returns None 829 """ 830 if prompt is None: 831 msg = u'(CTRL-C aborts)' 832 else: 833 msg = u'%s (CTRL-C aborts)' % prompt 834 835 if default is None: 836 msg = msg + u': ' 837 else: 838 msg = u'%s [%s]: ' % (msg, default) 839 840 try: 841 usr_input = raw_input(msg) 842 except KeyboardInterrupt: 843 return None 844 845 if usr_input == '': 846 return default 847 848 return usr_input
849 850 #=========================================================================== 851 # image handling tools 852 #--------------------------------------------------------------------------- 853 # builtin (ugly but tried and true) fallback icon 854 __icon_serpent = \ 855 """x\xdae\x8f\xb1\x0e\x83 \x10\x86w\x9f\xe2\x92\x1blb\xf2\x07\x96\xeaH:0\xd6\ 856 \xc1\x85\xd5\x98N5\xa5\xef?\xf5N\xd0\x8a\xdcA\xc2\xf7qw\x84\xdb\xfa\xb5\xcd\ 857 \xd4\xda;\xc9\x1a\xc8\xb6\xcd<\xb5\xa0\x85\x1e\xeb\xbc\xbc7b!\xf6\xdeHl\x1c\ 858 \x94\x073\xec<*\xf7\xbe\xf7\x99\x9d\xb21~\xe7.\xf5\x1f\x1c\xd3\xbdVlL\xc2\ 859 \xcf\xf8ye\xd0\x00\x90\x0etH \x84\x80B\xaa\x8a\x88\x85\xc4(U\x9d$\xfeR;\xc5J\ 860 \xa6\x01\xbbt9\xceR\xc8\x81e_$\x98\xb9\x9c\xa9\x8d,y\xa9t\xc8\xcf\x152\xe0x\ 861 \xe9$\xf5\x07\x95\x0cD\x95t:\xb1\x92\xae\x9cI\xa8~\x84\x1f\xe0\xa3ec""" 862
863 -def get_icon(wx=None):
864 865 paths = gmPaths(app_name = u'gnumed', wx = wx) 866 867 candidates = [ 868 os.path.join(paths.system_app_data_dir, 'bitmaps', 'gm_icon-serpent_and_gnu.png'), 869 os.path.join(paths.local_base_dir, 'bitmaps', 'gm_icon-serpent_and_gnu.png'), 870 os.path.join(paths.system_app_data_dir, 'bitmaps', 'serpent.png'), 871 os.path.join(paths.local_base_dir, 'bitmaps', 'serpent.png') 872 ] 873 874 found_as = None 875 for candidate in candidates: 876 try: 877 open(candidate, 'r').close() 878 found_as = candidate 879 break 880 except IOError: 881 _log.debug('icon not found in [%s]', candidate) 882 883 if found_as is None: 884 _log.warning('no icon file found, falling back to builtin (ugly) icon') 885 icon_bmp_data = wx.BitmapFromXPMData(cPickle.loads(zlib.decompress(__icon_serpent))) 886 icon.CopyFromBitmap(icon_bmp_data) 887 else: 888 _log.debug('icon found in [%s]', found_as) 889 icon = wx.EmptyIcon() 890 try: 891 icon.LoadFile(found_as, wx.BITMAP_TYPE_ANY) #_PNG 892 except AttributeError: 893 _log.exception(u"this platform doesn't support wx.Icon().LoadFile()") 894 895 return icon
896 #=========================================================================== 897 # main 898 #--------------------------------------------------------------------------- 899 if __name__ == '__main__': 900 901 if len(sys.argv) < 2: 902 sys.exit() 903 904 if sys.argv[1] != 'test': 905 sys.exit() 906 907 #-----------------------------------------------------------------------
908 - def test_input2decimal():
909 910 tests = [ 911 [None, False], 912 913 ['', False], 914 [' 0 ', True, 0], 915 916 [0, True, 0], 917 [0.0, True, 0], 918 [.0, True, 0], 919 ['0', True, 0], 920 ['0.0', True, 0], 921 ['0,0', True, 0], 922 ['00.0', True, 0], 923 ['.0', True, 0], 924 [',0', True, 0], 925 926 [0.1, True, decimal.Decimal('0.1')], 927 [.01, True, decimal.Decimal('0.01')], 928 ['0.1', True, decimal.Decimal('0.1')], 929 ['0,1', True, decimal.Decimal('0.1')], 930 ['00.1', True, decimal.Decimal('0.1')], 931 ['.1', True, decimal.Decimal('0.1')], 932 [',1', True, decimal.Decimal('0.1')], 933 934 [1, True, 1], 935 [1.0, True, 1], 936 ['1', True, 1], 937 ['1.', True, 1], 938 ['1,', True, 1], 939 ['1.0', True, 1], 940 ['1,0', True, 1], 941 ['01.0', True, 1], 942 ['01,0', True, 1], 943 [' 01, ', True, 1], 944 945 [decimal.Decimal('1.1'), True, decimal.Decimal('1.1')] 946 ] 947 for test in tests: 948 conversion_worked, result = input2decimal(initial = test[0]) 949 950 expected2work = test[1] 951 952 if conversion_worked: 953 if expected2work: 954 if result == test[2]: 955 continue 956 else: 957 print "ERROR (conversion result wrong): >%s<, expected >%s<, got >%s<" % (test[0], test[2], result) 958 else: 959 print "ERROR (conversion worked but was expected to fail): >%s<, got >%s<" % (test[0], result) 960 else: 961 if not expected2work: 962 continue 963 else: 964 print "ERROR (conversion failed but was expected to work): >%s<, expected >%s<" % (test[0], test[2])
965 #-----------------------------------------------------------------------
966 - def test_input2int():
967 print input2int(0) 968 print input2int('0') 969 print input2int(u'0', 0, 0)
970 #-----------------------------------------------------------------------
971 - def test_coalesce():
972 973 import datetime as dt 974 print coalesce(initial = dt.datetime.now(), template_initial = u'-- %s --', function_initial = ('strftime', u'%Y-%m-%d')) 975 976 print 'testing coalesce()' 977 print "------------------" 978 tests = [ 979 [None, 'something other than <None>', None, None, 'something other than <None>'], 980 ['Captain', 'Mr.', '%s.'[:4], 'Mr.', 'Capt.'], 981 ['value to test', 'test 3 failed', 'template with "%s" included', None, 'template with "value to test" included'], 982 ['value to test', 'test 4 failed', 'template with value not included', None, 'template with value not included'], 983 [None, 'initial value was None', 'template_initial: %s', None, 'initial value was None'], 984 [None, 'initial value was None', 'template_initial: %%(abc)s', None, 'initial value was None'] 985 ] 986 passed = True 987 for test in tests: 988 result = coalesce ( 989 initial = test[0], 990 instead = test[1], 991 template_initial = test[2], 992 template_instead = test[3] 993 ) 994 if result != test[4]: 995 print "ERROR" 996 print "coalesce: (%s, %s, %s, %s)" % (test[0], test[1], test[2], test[3]) 997 print "expected:", test[4] 998 print "received:", result 999 passed = False 1000 1001 if passed: 1002 print "passed" 1003 else: 1004 print "failed" 1005 return passed
1006 #-----------------------------------------------------------------------
1007 - def test_capitalize():
1008 print 'testing capitalize() ...' 1009 success = True 1010 pairs = [ 1011 # [original, expected result, CAPS mode] 1012 [u'Boot', u'Boot', CAPS_FIRST_ONLY], 1013 [u'boot', u'Boot', CAPS_FIRST_ONLY], 1014 [u'booT', u'Boot', CAPS_FIRST_ONLY], 1015 [u'BoOt', u'Boot', CAPS_FIRST_ONLY], 1016 [u'boots-Schau', u'Boots-Schau', CAPS_WORDS], 1017 [u'boots-sChau', u'Boots-Schau', CAPS_WORDS], 1018 [u'boot camp', u'Boot Camp', CAPS_WORDS], 1019 [u'fahrner-Kampe', u'Fahrner-Kampe', CAPS_NAMES], 1020 [u'häkkönen', u'Häkkönen', CAPS_NAMES], 1021 [u'McBurney', u'McBurney', CAPS_NAMES], 1022 [u'mcBurney', u'McBurney', CAPS_NAMES], 1023 [u'blumberg', u'Blumberg', CAPS_NAMES], 1024 [u'roVsing', u'RoVsing', CAPS_NAMES], 1025 [u'Özdemir', u'Özdemir', CAPS_NAMES], 1026 [u'özdemir', u'Özdemir', CAPS_NAMES], 1027 ] 1028 for pair in pairs: 1029 result = capitalize(pair[0], pair[2]) 1030 if result != pair[1]: 1031 success = False 1032 print 'ERROR (caps mode %s): "%s" -> "%s", expected "%s"' % (pair[2], pair[0], result, pair[1]) 1033 1034 if success: 1035 print "... SUCCESS" 1036 1037 return success
1038 #-----------------------------------------------------------------------
1039 - def test_import_module():
1040 print "testing import_module_from_directory()" 1041 path = sys.argv[1] 1042 name = sys.argv[2] 1043 try: 1044 mod = import_module_from_directory(module_path = path, module_name = name) 1045 except: 1046 print "module import failed, see log" 1047 return False 1048 1049 print "module import succeeded", mod 1050 print dir(mod) 1051 return True
1052 #-----------------------------------------------------------------------
1053 - def test_mkdir():
1054 print "testing mkdir()" 1055 mkdir(sys.argv[1])
1056 #-----------------------------------------------------------------------
1057 - def test_gmPaths():
1058 print "testing gmPaths()" 1059 print "-----------------" 1060 paths = gmPaths(wx=None, app_name='gnumed') 1061 print "user config dir:", paths.user_config_dir 1062 print "system config dir:", paths.system_config_dir 1063 print "local base dir:", paths.local_base_dir 1064 print "system app data dir:", paths.system_app_data_dir 1065 print "working directory :", paths.working_dir 1066 print "temp directory :", paths.tmp_dir
1067 #-----------------------------------------------------------------------
1068 - def test_none_if():
1069 print "testing none_if()" 1070 print "-----------------" 1071 tests = [ 1072 [None, None, None], 1073 ['a', 'a', None], 1074 ['a', 'b', 'a'], 1075 ['a', None, 'a'], 1076 [None, 'a', None], 1077 [1, 1, None], 1078 [1, 2, 1], 1079 [1, None, 1], 1080 [None, 1, None] 1081 ] 1082 1083 for test in tests: 1084 if none_if(value = test[0], none_equivalent = test[1]) != test[2]: 1085 print 'ERROR: none_if(%s) returned [%s], expected [%s]' % (test[0], none_if(test[0], test[1]), test[2]) 1086 1087 return True
1088 #-----------------------------------------------------------------------
1089 - def test_bool2str():
1090 tests = [ 1091 [True, 'Yes', 'Yes', 'Yes'], 1092 [False, 'OK', 'not OK', 'not OK'] 1093 ] 1094 for test in tests: 1095 if bool2str(test[0], test[1], test[2]) != test[3]: 1096 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]) 1097 1098 return True
1099 #-----------------------------------------------------------------------
1100 - def test_bool2subst():
1101 1102 print bool2subst(True, 'True', 'False', 'is None') 1103 print bool2subst(False, 'True', 'False', 'is None') 1104 print bool2subst(None, 'True', 'False', 'is None')
1105 #-----------------------------------------------------------------------
1106 - def test_get_unique_filename():
1107 print get_unique_filename() 1108 print get_unique_filename(prefix='test-') 1109 print get_unique_filename(suffix='tst') 1110 print get_unique_filename(prefix='test-', suffix='tst') 1111 print get_unique_filename(tmp_dir='/home/ncq/Archiv/')
1112 #-----------------------------------------------------------------------
1113 - def test_size2str():
1114 print "testing size2str()" 1115 print "------------------" 1116 tests = [0, 1, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000, 1000000000000, 10000000000000] 1117 for test in tests: 1118 print size2str(test)
1119 #-----------------------------------------------------------------------
1120 - def test_unwrap():
1121 1122 test = """ 1123 second line\n 1124 3rd starts with tab \n 1125 4th with a space \n 1126 1127 6th 1128 1129 """ 1130 print unwrap(text = test, max_length = 25)
1131 #-----------------------------------------------------------------------
1132 - def test_wrap():
1133 test = 'line 1\nline 2\nline 3' 1134 1135 print "wrap 5-6-7 initial 0, subsequent 0" 1136 print wrap(test, 5) 1137 print 1138 print wrap(test, 6) 1139 print 1140 print wrap(test, 7) 1141 print "-------" 1142 raw_input() 1143 print "wrap 5 initial 1-1-3, subsequent 1-3-1" 1144 print wrap(test, 5, u' ', u' ') 1145 print 1146 print wrap(test, 5, u' ', u' ') 1147 print 1148 print wrap(test, 5, u' ', u' ') 1149 print "-------" 1150 raw_input() 1151 print "wrap 6 initial 1-1-3, subsequent 1-3-1" 1152 print wrap(test, 6, u' ', u' ') 1153 print 1154 print wrap(test, 6, u' ', u' ') 1155 print 1156 print wrap(test, 6, u' ', u' ') 1157 print "-------" 1158 raw_input() 1159 print "wrap 7 initial 1-1-3, subsequent 1-3-1" 1160 print wrap(test, 7, u' ', u' ') 1161 print 1162 print wrap(test, 7, u' ', u' ') 1163 print 1164 print wrap(test, 7, u' ', u' ')
1165 #-----------------------------------------------------------------------
1166 - def test_md5():
1167 print '%s: %s' % (sys.argv[2], file2md5(sys.argv[2]))
1168 #-----------------------------------------------------------------------
1169 - def test_unicode():
1170 print u_link_symbol * 10
1171 #-----------------------------------------------------------------------
1172 - def test_xml_escape():
1173 print xml_escape_string(u'<') 1174 print xml_escape_string(u'>') 1175 print xml_escape_string(u'&')
1176 #-----------------------------------------------------------------------
1177 - def test_tex_escape():
1178 tests = [u'\\', u'^', u'~', u'{', u'}', u'%', u'&', u'#', u'$', u'_', u_euro] 1179 tests.append(u' '.join(tests)) 1180 for test in tests: 1181 print u'%s:' % test, tex_escape_string(test)
1182 #-----------------------------------------------------------------------
1183 - def test_gpg_decrypt():
1184 fname = gpg_decrypt_file(filename = sys.argv[2], passphrase = sys.argv[3]) 1185 if fname is not None: 1186 print "successfully decrypted:", fname
1187 #-----------------------------------------------------------------------
1188 - def test_strip_trailing_empty_lines():
1189 tests = [ 1190 u'one line, no embedded line breaks ', 1191 u'one line\nwith embedded\nline\nbreaks\n ' 1192 ] 1193 for test in tests: 1194 print 'as list:' 1195 print strip_trailing_empty_lines(text = test, eol=u'\n', return_list = True) 1196 print 'as string:' 1197 print u'>>>%s<<<' % strip_trailing_empty_lines(text = test, eol=u'\n', return_list = False) 1198 tests = [ 1199 ['list', 'without', 'empty', 'trailing', 'lines'], 1200 ['list', 'with', 'empty', 'trailing', 'lines', '', ' ', ''] 1201 ] 1202 for test in tests: 1203 print 'as list:' 1204 print strip_trailing_empty_lines(lines = test, eol = u'\n', return_list = True) 1205 print 'as string:' 1206 print strip_trailing_empty_lines(lines = test, eol = u'\n', return_list = False)
1207 #-----------------------------------------------------------------------
1208 - def test_fname_stem():
1209 tests = [ 1210 r'abc.exe', 1211 r'\abc.exe', 1212 r'c:\abc.exe', 1213 r'c:\d\abc.exe', 1214 r'/home/ncq/tmp.txt', 1215 r'~/tmp.txt', 1216 r'./tmp.txt', 1217 r'./.././tmp.txt', 1218 r'tmp.txt' 1219 ] 1220 for t in tests: 1221 print "[%s] -> [%s]" % (t, fname_stem(t))
1222 #----------------------------------------------------------------------- 1223 #test_coalesce() 1224 #test_capitalize() 1225 #test_import_module() 1226 #test_mkdir() 1227 #test_gmPaths() 1228 #test_none_if() 1229 #test_bool2str() 1230 #test_bool2subst() 1231 #test_get_unique_filename() 1232 #test_size2str() 1233 #test_wrap() 1234 #test_input2decimal() 1235 #test_input2int() 1236 #test_unwrap() 1237 #test_md5() 1238 #test_unicode() 1239 #test_xml_escape() 1240 #test_gpg_decrypt() 1241 #test_strip_trailing_empty_lines() 1242 test_fname_stem() 1243 #test_tex_escape() 1244 1245 #=========================================================================== 1246