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