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  __version__ = "$Revision: 1.98 $" 
   6  __author__ = "K. Hilbert <Karsten.Hilbert@gmx.net>" 
   7  __license__ = "GPL (details at http://www.gnu.org)" 
   8   
   9  # std libs 
  10  import re as regex, sys, os, os.path, csv, tempfile, logging, hashlib 
  11  import urllib2 as wget, decimal, StringIO, MimeWriter, mimetypes, mimetools 
  12  import cPickle, zlib 
  13   
  14   
  15  # GNUmed libs 
  16  if __name__ == '__main__': 
  17          # for testing: 
  18          logging.basicConfig(level = logging.DEBUG) 
  19          sys.path.insert(0, '../../') 
  20          from Gnumed.pycommon import gmI18N 
  21          gmI18N.activate_locale() 
  22          gmI18N.install_domain() 
  23   
  24  from Gnumed.pycommon import gmBorg 
  25   
  26   
  27  _log = logging.getLogger('gm.tools') 
  28  _log.info(__version__) 
  29   
  30  # CAPitalization modes: 
  31  (       CAPS_NONE,                                      # don't touch it 
  32          CAPS_FIRST,                                     # CAP first char, leave rest as is 
  33          CAPS_ALLCAPS,                           # CAP all chars 
  34          CAPS_WORDS,                                     # CAP first char of every word 
  35          CAPS_NAMES,                                     # CAP in a way suitable for names (tries to be smart) 
  36          CAPS_FIRST_ONLY                         # CAP first char, lowercase the rest 
  37  ) = range(6) 
  38   
  39  default_mail_sender = u'gnumed@gmx.net' 
  40  default_mail_receiver = u'gnumed-devel@gnu.org' 
  41  default_mail_server = u'mail.gmx.net' 
  42   
  43   
  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_ellipsis = u'\u2026' 
  52  u_left_arrow = u'\u2190' 
  53  u_right_arrow = u'\u2192' 
  54  u_corresponds_to = u'\u2258' 
  55  u_infinity = u'\u221E' 
  56  u_diameter = u'\u2300' 
  57  u_checkmark_crossed_out = u'\u237B' 
  58  u_frowning_face = u'\u2639' 
  59  u_smiling_face = u'\u263a' 
  60  u_black_heart = u'\u2665' 
  61  u_checkmark_thin = u'\u2713' 
  62  u_checkmark_thick = u'\u2714' 
  63  u_writing_hand = u'\u270d' 
  64  u_pencil_1 = u'\u270e' 
  65  u_pencil_2 = u'\u270f' 
  66  u_pencil_3 = u'\u2710' 
  67  u_latin_cross = u'\u271d' 
  68  u_replacement_character = u'\ufffd' 
  69   
  70  #=========================================================================== 
71 -def check_for_update(url=None, current_branch=None, current_version=None, consider_latest_branch=False):
72 """Check for new releases at <url>. 73 74 Returns (bool, text). 75 True: new release available 76 False: up to date 77 None: don't know 78 """ 79 try: 80 remote_file = wget.urlopen(url) 81 except (wget.URLError, ValueError, OSError): 82 _log.exception("cannot retrieve version file from [%s]", url) 83 return (None, _('Cannot retrieve version information from:\n\n%s') % url) 84 85 _log.debug('retrieving version information from [%s]', url) 86 87 from Gnumed.pycommon import gmCfg2 88 cfg = gmCfg2.gmCfgData() 89 try: 90 cfg.add_stream_source(source = 'gm-versions', stream = remote_file) 91 except (UnicodeDecodeError): 92 remote_file.close() 93 _log.exception("cannot read version file from [%s]", url) 94 return (None, _('Cannot read version information from:\n\n%s') % url) 95 96 remote_file.close() 97 98 latest_branch = cfg.get('latest branch', 'branch', source_order = [('gm-versions', 'return')]) 99 latest_release_on_latest_branch = cfg.get('branch %s' % latest_branch, 'latest release', source_order = [('gm-versions', 'return')]) 100 latest_release_on_current_branch = cfg.get('branch %s' % current_branch, 'latest release', source_order = [('gm-versions', 'return')]) 101 102 cfg.remove_source('gm-versions') 103 104 _log.info('current release: %s', current_version) 105 _log.info('current branch: %s', current_branch) 106 _log.info('latest release on current branch: %s', latest_release_on_current_branch) 107 _log.info('latest branch: %s', latest_branch) 108 _log.info('latest release on latest branch: %s', latest_release_on_latest_branch) 109 110 # anything known ? 111 no_release_information_available = ( 112 ( 113 (latest_release_on_current_branch is None) and 114 (latest_release_on_latest_branch is None) 115 ) or ( 116 not consider_latest_branch and 117 (latest_release_on_current_branch is None) 118 ) 119 ) 120 if no_release_information_available: 121 _log.warning('no release information available') 122 msg = _('There is no version information available from:\n\n%s') % url 123 return (None, msg) 124 125 # up to date ? 126 if consider_latest_branch: 127 _log.debug('latest branch taken into account') 128 if current_version >= latest_release_on_latest_branch: 129 _log.debug('up to date: current version >= latest version on latest branch') 130 return (False, None) 131 if latest_release_on_latest_branch is None: 132 if current_version >= latest_release_on_current_branch: 133 _log.debug('up to date: current version >= latest version on current branch and no latest branch available') 134 return (False, None) 135 else: 136 _log.debug('latest branch not taken into account') 137 if current_version >= latest_release_on_current_branch: 138 _log.debug('up to date: current version >= latest version on current branch') 139 return (False, None) 140 141 new_release_on_current_branch_available = ( 142 (latest_release_on_current_branch is not None) and 143 (latest_release_on_current_branch > current_version) 144 ) 145 _log.info('%snew release on current branch available', bool2str(new_release_on_current_branch_available, '', 'no ')) 146 147 new_release_on_latest_branch_available = ( 148 (latest_branch is not None) 149 and 150 ( 151 (latest_branch > current_branch) or ( 152 (latest_branch == current_branch) and 153 (latest_release_on_latest_branch > current_version) 154 ) 155 ) 156 ) 157 _log.info('%snew release on latest branch available', bool2str(new_release_on_latest_branch_available, '', 'no ')) 158 159 if not (new_release_on_current_branch_available or new_release_on_latest_branch_available): 160 _log.debug('up to date: no new releases available') 161 return (False, None) 162 163 # not up to date 164 msg = _('A new version of GNUmed is available.\n\n') 165 msg += _(' Your current version: "%s"\n') % current_version 166 if consider_latest_branch: 167 if new_release_on_current_branch_available: 168 msg += u'\n' 169 msg += _(' New version: "%s"') % latest_release_on_current_branch 170 msg += u'\n' 171 msg += _(' - bug fixes only\n') 172 msg += _(' - database fixups may be needed\n') 173 if new_release_on_latest_branch_available: 174 if current_branch != latest_branch: 175 msg += u'\n' 176 msg += _(' New version: "%s"') % latest_release_on_latest_branch 177 msg += u'\n' 178 msg += _(' - bug fixes and new features\n') 179 msg += _(' - database upgrade required\n') 180 else: 181 msg += u'\n' 182 msg += _(' New version: "%s"') % latest_release_on_current_branch 183 msg += u'\n' 184 msg += _(' - bug fixes only\n') 185 msg += _(' - database fixups may be needed\n') 186 187 msg += u'\n\n' 188 msg += _( 189 'Note, however, that this version may not yet\n' 190 'be available *pre-packaged* for your system.' 191 ) 192 193 msg += u'\n\n' 194 msg += _('Details are found on <http://wiki.gnumed.de>.\n') 195 msg += u'\n' 196 msg += _('Version information loaded from:\n\n %s') % url 197 198 return (True, msg)
199 #===========================================================================
200 -def handle_uncaught_exception_console(t, v, tb):
201 202 print ".========================================================" 203 print "| Unhandled exception caught !" 204 print "| Type :", t 205 print "| Value:", v 206 print "`========================================================" 207 _log.critical('unhandled exception caught', exc_info = (t,v,tb)) 208 sys.__excepthook__(t,v,tb)
209 #=========================================================================== 210 # path level operations 211 #---------------------------------------------------------------------------
212 -def mkdir(directory=None):
213 try: 214 os.makedirs(directory) 215 except OSError, e: 216 if (e.errno == 17) and not os.path.isdir(directory): 217 raise 218 return True
219 220 #---------------------------------------------------------------------------
221 -class gmPaths(gmBorg.cBorg):
222 """This class provides the following paths: 223 224 .home_dir 225 .local_base_dir 226 .working_dir 227 .user_config_dir 228 .system_config_dir 229 .system_app_data_dir 230 """
231 - def __init__(self, app_name=None, wx=None):
232 """Setup pathes. 233 234 <app_name> will default to (name of the script - .py) 235 """ 236 try: 237 self.already_inited 238 return 239 except AttributeError: 240 pass 241 242 self.init_paths(app_name=app_name, wx=wx) 243 self.already_inited = True
244 #-------------------------------------- 245 # public API 246 #--------------------------------------
247 - def init_paths(self, app_name=None, wx=None):
248 249 if wx is None: 250 _log.debug('wxPython not available') 251 _log.debug('detecting paths directly') 252 253 if app_name is None: 254 app_name, ext = os.path.splitext(os.path.basename(sys.argv[0])) 255 _log.info('app name detected as [%s]', app_name) 256 else: 257 _log.info('app name passed in as [%s]', app_name) 258 259 # the user home, doesn't work in Wine so work around that 260 self.__home_dir = None 261 262 # where the main script (the "binary") is installed 263 #self.local_base_dir = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '..', '.')) 264 self.local_base_dir = os.path.abspath(os.path.dirname(sys.argv[0])) 265 266 # the current working dir at the OS 267 self.working_dir = os.path.abspath(os.curdir) 268 269 # user-specific config dir, usually below the home dir 270 #mkdir(os.path.expanduser(os.path.join('~', '.%s' % app_name))) 271 #self.user_config_dir = os.path.expanduser(os.path.join('~', '.%s' % app_name)) 272 mkdir(os.path.join(self.home_dir, '.%s' % app_name)) 273 self.user_config_dir = os.path.join(self.home_dir, '.%s' % app_name) 274 275 # system-wide config dir, usually below /etc/ under UN*X 276 try: 277 self.system_config_dir = os.path.join('/etc', app_name) 278 except ValueError: 279 #self.system_config_dir = self.local_base_dir 280 self.system_config_dir = self.user_config_dir 281 282 # system-wide application data dir 283 try: 284 self.system_app_data_dir = os.path.join(sys.prefix, 'share', app_name) 285 except ValueError: 286 self.system_app_data_dir = self.local_base_dir 287 288 self.__log_paths() 289 if wx is None: 290 return True 291 292 # retry with wxPython 293 _log.debug('re-detecting paths with wxPython') 294 295 std_paths = wx.StandardPaths.Get() 296 _log.info('wxPython app name is [%s]', wx.GetApp().GetAppName()) 297 298 # user-specific config dir, usually below the home dir 299 mkdir(os.path.join(std_paths.GetUserConfigDir(), '.%s' % app_name)) 300 self.user_config_dir = os.path.join(std_paths.GetUserConfigDir(), '.%s' % app_name) 301 302 # system-wide config dir, usually below /etc/ under UN*X 303 try: 304 tmp = std_paths.GetConfigDir() 305 if not tmp.endswith(app_name): 306 tmp = os.path.join(tmp, app_name) 307 self.system_config_dir = tmp 308 except ValueError: 309 # leave it at what it was from direct detection 310 pass 311 312 # system-wide application data dir 313 # Robin attests that the following doesn't always 314 # give sane values on Windows, so IFDEF it 315 if 'wxMSW' in wx.PlatformInfo: 316 _log.warning('this platform (wxMSW) sometimes returns a broken value for the system-wide application data dir') 317 else: 318 try: 319 self.system_app_data_dir = std_paths.GetDataDir() 320 except ValueError: 321 pass 322 323 self.__log_paths() 324 return True
325 #--------------------------------------
326 - def __log_paths(self):
327 _log.debug('sys.argv[0]: %s', sys.argv[0]) 328 _log.debug('local application base dir: %s', self.local_base_dir) 329 _log.debug('current working dir: %s', self.working_dir) 330 #_log.debug('user home dir: %s', os.path.expanduser('~')) 331 _log.debug('user home dir: %s', self.home_dir) 332 _log.debug('user-specific config dir: %s', self.user_config_dir) 333 _log.debug('system-wide config dir: %s', self.system_config_dir) 334 _log.debug('system-wide application data dir: %s', self.system_app_data_dir)
335 #-------------------------------------- 336 # properties 337 #--------------------------------------
338 - def _set_user_config_dir(self, path):
339 if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)): 340 msg = '[%s:user_config_dir]: invalid path [%s]' % (self.__class__.__name__, path) 341 _log.error(msg) 342 raise ValueError(msg) 343 self.__user_config_dir = path
344
345 - def _get_user_config_dir(self):
346 return self.__user_config_dir
347 348 user_config_dir = property(_get_user_config_dir, _set_user_config_dir) 349 #--------------------------------------
350 - def _set_system_config_dir(self, path):
351 if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)): 352 msg = '[%s:system_config_dir]: invalid path [%s]' % (self.__class__.__name__, path) 353 _log.error(msg) 354 raise ValueError(msg) 355 self.__system_config_dir = path
356
357 - def _get_system_config_dir(self):
358 return self.__system_config_dir
359 360 system_config_dir = property(_get_system_config_dir, _set_system_config_dir) 361 #--------------------------------------
362 - def _set_system_app_data_dir(self, path):
363 if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)): 364 msg = '[%s:system_app_data_dir]: invalid path [%s]' % (self.__class__.__name__, path) 365 _log.error(msg) 366 raise ValueError(msg) 367 self.__system_app_data_dir = path
368
369 - def _get_system_app_data_dir(self):
370 return self.__system_app_data_dir
371 372 system_app_data_dir = property(_get_system_app_data_dir, _set_system_app_data_dir) 373 #--------------------------------------
374 - def _set_home_dir(self, path):
375 raise ArgumentError('invalid to set home dir')
376
377 - def _get_home_dir(self):
378 if self.__home_dir is not None: 379 return self.__home_dir 380 381 tmp = os.path.expanduser('~') 382 if tmp == '~': 383 _log.error('this platform does not expand ~ properly') 384 try: 385 tmp = os.environ['USERPROFILE'] 386 except KeyError: 387 _log.error('cannot access $USERPROFILE in environment') 388 389 if not ( 390 os.access(tmp, os.R_OK) 391 and 392 os.access(tmp, os.X_OK) 393 and 394 os.access(tmp, os.W_OK) 395 ): 396 msg = '[%s:home_dir]: invalid path [%s]' % (self.__class__.__name__, tmp) 397 _log.error(msg) 398 raise ValueError(msg) 399 400 self.__home_dir = tmp 401 return self.__home_dir
402 403 home_dir = property(_get_home_dir, _set_home_dir)
404 #===========================================================================
405 -def send_mail(sender=None, receiver=None, message=None, server=None, auth=None, debug=False, subject=None, encoding='quoted-printable', attachments=None):
406 407 if message is None: 408 return False 409 410 message = message.lstrip().lstrip('\r\n').lstrip() 411 412 if sender is None: 413 sender = default_mail_sender 414 415 if receiver is None: 416 receiver = [default_mail_receiver] 417 418 if server is None: 419 server = default_mail_server 420 421 if subject is None: 422 subject = u'gmTools.py: send_mail() test' 423 424 msg = StringIO.StringIO() 425 writer = MimeWriter.MimeWriter(msg) 426 writer.addheader('To', u', '.join(receiver)) 427 writer.addheader('From', sender) 428 writer.addheader('Subject', subject[:50].replace('\r', '/').replace('\n', '/')) 429 writer.addheader('MIME-Version', '1.0') 430 431 writer.startmultipartbody('mixed') 432 433 # start with a text/plain part 434 part = writer.nextpart() 435 body = part.startbody('text/plain') 436 part.flushheaders() 437 body.write(message.encode(encoding)) 438 439 # now add the attachments 440 if attachments is not None: 441 for a in attachments: 442 filename = os.path.basename(a[0]) 443 try: 444 mtype = a[1] 445 encoding = a[2] 446 except IndexError: 447 mtype, encoding = mimetypes.guess_type(a[0]) 448 if mtype is None: 449 mtype = 'application/octet-stream' 450 encoding = 'base64' 451 elif mtype == 'text/plain': 452 encoding = 'quoted-printable' 453 else: 454 encoding = 'base64' 455 456 part = writer.nextpart() 457 part.addheader('Content-Transfer-Encoding', encoding) 458 body = part.startbody("%s; name=%s" % (mtype, filename)) 459 mimetools.encode(open(a[0], 'rb'), body, encoding) 460 461 writer.lastpart() 462 463 import smtplib 464 session = smtplib.SMTP(server) 465 session.set_debuglevel(debug) 466 if auth is not None: 467 session.login(auth['user'], auth['password']) 468 refused = session.sendmail(sender, receiver, msg.getvalue()) 469 session.quit() 470 msg.close() 471 if len(refused) != 0: 472 _log.error("refused recipients: %s" % refused) 473 return False 474 475 return True
476 #-------------------------------------------------------------------------------
477 -def send_mail_old(sender=None, receiver=None, message=None, server=None, auth=None, debug=False, subject=None, encoding='latin1'):
478 """Send an E-Mail. 479 480 <debug>: see smtplib.set_debuglevel() 481 <auth>: {'user': ..., 'password': ...} 482 <receiver>: a list of email addresses 483 """ 484 if message is None: 485 return False 486 message = message.lstrip().lstrip('\r\n').lstrip() 487 488 if sender is None: 489 sender = default_mail_sender 490 491 if receiver is None: 492 receiver = [default_mail_receiver] 493 494 if server is None: 495 server = default_mail_server 496 497 if subject is None: 498 subject = u'gmTools.py: send_mail() test' 499 500 body = u"""From: %s 501 To: %s 502 Subject: %s 503 504 %s 505 """ % (sender, u', '.join(receiver), subject[:50].replace('\r', '/').replace('\n', '/'), message) 506 507 import smtplib 508 session = smtplib.SMTP(server) 509 session.set_debuglevel(debug) 510 if auth is not None: 511 session.login(auth['user'], auth['password']) 512 refused = session.sendmail(sender, receiver, body.encode(encoding)) 513 session.quit() 514 if len(refused) != 0: 515 _log.error("refused recipients: %s" % refused) 516 return False 517 518 return True
519 #=========================================================================== 520 # file related tools 521 #---------------------------------------------------------------------------
522 -def file2md5(filename=None, return_hex=True):
523 blocksize = 2**10 * 128 # 128k, since md5 use 128 byte blocks 524 _log.debug('md5(%s): <%s> byte blocks', filename, blocksize) 525 526 f = open(filename, 'rb') 527 528 md5 = hashlib.md5() 529 while True: 530 data = f.read(blocksize) 531 if not data: 532 break 533 md5.update(data) 534 535 _log.debug('md5(%s): %s', filename, md5.hexdigest()) 536 537 if return_hex: 538 return md5.hexdigest() 539 return md5.digest()
540 #---------------------------------------------------------------------------
541 -def unicode2charset_encoder(unicode_csv_data, encoding='utf-8'):
542 for line in unicode_csv_data: 543 yield line.encode(encoding)
544 545 #def utf_8_encoder(unicode_csv_data): 546 # for line in unicode_csv_data: 547 # yield line.encode('utf-8') 548
549 -def unicode_csv_reader(unicode_csv_data, dialect=csv.excel, encoding='utf-8', **kwargs):
550 # csv.py doesn't do Unicode; encode temporarily as UTF-8: 551 try: 552 is_dict_reader = kwargs['dict'] 553 del kwargs['dict'] 554 if is_dict_reader is not True: 555 raise KeyError 556 csv_reader = csv.DictReader(unicode2charset_encoder(unicode_csv_data), dialect=dialect, **kwargs) 557 except KeyError: 558 is_dict_reader = False 559 csv_reader = csv.reader(unicode2charset_encoder(unicode_csv_data), dialect=dialect, **kwargs) 560 561 for row in csv_reader: 562 # decode ENCODING back to Unicode, cell by cell: 563 if is_dict_reader: 564 for key in row.keys(): 565 row[key] = unicode(row[key], encoding) 566 yield row 567 else: 568 yield [ unicode(cell, encoding) for cell in row ]
569 #yield [unicode(cell, 'utf-8') for cell in row] 570 #---------------------------------------------------------------------------
571 -def get_unique_filename(prefix=None, suffix=None, tmp_dir=None):
572 """This introduces a race condition between the file.close() and 573 actually using the filename. 574 575 The file will not exist after calling this function. 576 """ 577 if tmp_dir is not None: 578 if ( 579 not os.access(tmp_dir, os.F_OK) 580 or 581 not os.access(tmp_dir, os.X_OK | os.W_OK) 582 ): 583 _log.info('cannot find temporary dir [%s], using system default', tmp_dir) 584 tmp_dir = None 585 586 kwargs = {'dir': tmp_dir} 587 588 if prefix is None: 589 kwargs['prefix'] = 'gnumed-' 590 else: 591 kwargs['prefix'] = prefix 592 593 if suffix in [None, u'']: 594 kwargs['suffix'] = '.tmp' 595 else: 596 if not suffix.startswith('.'): 597 suffix = '.' + suffix 598 kwargs['suffix'] = suffix 599 600 f = tempfile.NamedTemporaryFile(**kwargs) 601 filename = f.name 602 f.close() 603 604 return filename
605 #===========================================================================
606 -def import_module_from_directory(module_path=None, module_name=None, always_remove_path=False):
607 """Import a module from any location.""" 608 609 remove_path = always_remove_path or False 610 if module_path not in sys.path: 611 _log.info('appending to sys.path: [%s]' % module_path) 612 sys.path.append(module_path) 613 remove_path = True 614 615 _log.debug('will remove import path: %s', remove_path) 616 617 if module_name.endswith('.py'): 618 module_name = module_name[:-3] 619 620 try: 621 module = __import__(module_name) 622 except StandardError: 623 _log.exception('cannot __import__() module [%s] from [%s]' % (module_name, module_path)) 624 while module_path in sys.path: 625 sys.path.remove(module_path) 626 raise 627 628 _log.info('imported module [%s] as [%s]' % (module_name, module)) 629 if remove_path: 630 while module_path in sys.path: 631 sys.path.remove(module_path) 632 633 return module
634 #=========================================================================== 635 # text related tools 636 #--------------------------------------------------------------------------- 637 _kB = 1024 638 _MB = 1024 * _kB 639 _GB = 1024 * _MB 640 _TB = 1024 * _GB 641 _PB = 1024 * _TB 642 #---------------------------------------------------------------------------
643 -def size2str(size=0, template='%s'):
644 if size == 1: 645 return template % _('1 Byte') 646 if size < 10 * _kB: 647 return template % _('%s Bytes') % size 648 if size < _MB: 649 return template % u'%.1f kB' % (float(size) / _kB) 650 if size < _GB: 651 return template % u'%.1f MB' % (float(size) / _MB) 652 if size < _TB: 653 return template % u'%.1f GB' % (float(size) / _GB) 654 if size < _PB: 655 return template % u'%.1f TB' % (float(size) / _TB) 656 return template % u'%.1f PB' % (float(size) / _PB)
657 #---------------------------------------------------------------------------
658 -def bool2subst(boolean=None, true_return=True, false_return=False, none_return=None):
659 if boolean is None: 660 return none_return 661 if boolean is True: 662 return true_return 663 if boolean is False: 664 return false_return 665 raise ValueError('bool2subst(): <boolean> arg must be either of True, False, None')
666 #---------------------------------------------------------------------------
667 -def bool2str(boolean=None, true_str='True', false_str='False'):
668 return bool2subst ( 669 boolean = bool(boolean), 670 true_return = true_str, 671 false_return = false_str 672 )
673 #---------------------------------------------------------------------------
674 -def none_if(value=None, none_equivalent=None):
675 """Modelled after the SQL NULLIF function.""" 676 if value == none_equivalent: 677 return None 678 return value
679 #---------------------------------------------------------------------------
680 -def coalesce(initial=None, instead=None, template_initial=None, template_instead=None, none_equivalents=None):
681 """Modelled after the SQL coalesce function. 682 683 To be used to simplify constructs like: 684 685 if initial is None (or in none_equivalents): 686 real_value = (template_instead % instead) or instead 687 else: 688 real_value = (template_initial % initial) or initial 689 print real_value 690 691 @param initial: the value to be tested for <None> 692 @type initial: any Python type, must have a __str__ method if template_initial is not None 693 @param instead: the value to be returned if <initial> is None 694 @type instead: any Python type, must have a __str__ method if template_instead is not None 695 @param template_initial: if <initial> is returned replace the value into this template, must contain one <%s> 696 @type template_initial: string or None 697 @param template_instead: if <instead> is returned replace the value into this template, must contain one <%s> 698 @type template_instead: string or None 699 700 Ideas: 701 - list of insteads: initial, [instead, template], [instead, template], [instead, template], template_initial, ... 702 """ 703 if none_equivalents is None: 704 none_equivalents = [None] 705 706 if initial in none_equivalents: 707 708 if template_instead is None: 709 return instead 710 711 return template_instead % instead 712 713 if template_initial is None: 714 return initial 715 716 try: 717 return template_initial % initial 718 except TypeError: 719 return template_initial
720 #---------------------------------------------------------------------------
721 -def __cap_name(match_obj=None):
722 val = match_obj.group(0).lower() 723 if val in ['von', 'van', 'de', 'la', 'l', 'der', 'den']: # FIXME: this needs to expand, configurable ? 724 return val 725 buf = list(val) 726 buf[0] = buf[0].upper() 727 for part in ['mac', 'mc', 'de', 'la']: 728 if len(val) > len(part) and val[:len(part)] == part: 729 buf[len(part)] = buf[len(part)].upper() 730 return ''.join(buf)
731 #---------------------------------------------------------------------------
732 -def capitalize(text=None, mode=CAPS_NAMES):
733 """Capitalize the first character but leave the rest alone. 734 735 Note that we must be careful about the locale, this may 736 have issues ! However, for UTF strings it should just work. 737 """ 738 if (mode is None) or (mode == CAPS_NONE): 739 return text 740 741 if mode == CAPS_FIRST: 742 if len(text) == 1: 743 return text[0].upper() 744 return text[0].upper() + text[1:] 745 746 if mode == CAPS_ALLCAPS: 747 return text.upper() 748 749 if mode == CAPS_FIRST_ONLY: 750 if len(text) == 1: 751 return text[0].upper() 752 return text[0].upper() + text[1:].lower() 753 754 if mode == CAPS_WORDS: 755 return regex.sub(ur'(\w)(\w+)', lambda x: x.group(1).upper() + x.group(2).lower(), text) 756 757 if mode == CAPS_NAMES: 758 #return regex.sub(r'\w+', __cap_name, text) 759 return capitalize(text=text, mode=CAPS_FIRST) # until fixed 760 761 print "ERROR: invalid capitalization mode: [%s], leaving input as is" % mode 762 return text
763 #---------------------------------------------------------------------------
764 -def input2decimal(initial=None):
765 766 val = initial 767 768 # float ? -> to string first 769 if type(val) == type(1.4): 770 val = str(val) 771 772 # string ? -> "," to "." 773 if isinstance(val, basestring): 774 val = val.replace(',', '.', 1) 775 val = val.strip() 776 # val = val.lstrip('0') 777 # if val.startswith('.'): 778 # val = '0' + val 779 780 try: 781 d = decimal.Decimal(val) 782 return True, d 783 except (TypeError, decimal.InvalidOperation): 784 return False, val
785 #---------------------------------------------------------------------------
786 -def wrap(text=None, width=None, initial_indent=u'', subsequent_indent=u'', eol=u'\n'):
787 """A word-wrap function that preserves existing line breaks 788 and most spaces in the text. Expects that existing line 789 breaks are posix newlines (\n). 790 """ 791 wrapped = initial_indent + reduce ( 792 lambda line, word, width=width: '%s%s%s' % ( 793 line, 794 ' \n'[(len(line) - line.rfind('\n') - 1 + len(word.split('\n',1)[0]) >= width)], 795 word 796 ), 797 text.split(' ') 798 ) 799 800 if subsequent_indent != u'': 801 wrapped = (u'\n%s' % subsequent_indent).join(wrapped.split('\n')) 802 803 if eol != u'\n': 804 wrapped = wrapped.replace('\n', eol) 805 806 return wrapped
807 #---------------------------------------------------------------------------
808 -def unwrap(text=None, max_length=None, strip_whitespace=True, remove_empty_lines=True, line_separator = u' // '):
809 810 text = text.replace(u'\r', u'') 811 lines = text.split(u'\n') 812 text = u'' 813 for line in lines: 814 815 if strip_whitespace: 816 line = line.strip().strip(u'\t').strip() 817 818 if remove_empty_lines: 819 if line == u'': 820 continue 821 822 text += (u'%s%s' % (line, line_separator)) 823 824 text = text.rstrip(line_separator) 825 826 if max_length is not None: 827 text = text[:max_length] 828 829 text = text.rstrip(line_separator) 830 831 return text
832 #---------------------------------------------------------------------------
833 -def tex_escape_string(text=None):
834 """check for special latex-characters and transform them""" 835 836 text = text.replace(u'\\', u'$\\backslash$') 837 text = text.replace(u'{', u'\\{') 838 text = text.replace(u'}', u'\\}') 839 text = text.replace(u'%', u'\\%') 840 text = text.replace(u'&', u'\\&') 841 text = text.replace(u'#', u'\\#') 842 text = text.replace(u'$', u'\\$') 843 text = text.replace(u'_', u'\\_') 844 845 text = text.replace(u'^', u'\\verb#^#') 846 text = text.replace('~','\\verb#~#') 847 848 return text
849 #=========================================================================== 850 # image handling tools 851 #--------------------------------------------------------------------------- 852 # builtin (ugly but tried and true) fallback icon 853 __icon_serpent = \ 854 """x\xdae\x8f\xb1\x0e\x83 \x10\x86w\x9f\xe2\x92\x1blb\xf2\x07\x96\xeaH:0\xd6\ 855 \xc1\x85\xd5\x98N5\xa5\xef?\xf5N\xd0\x8a\xdcA\xc2\xf7qw\x84\xdb\xfa\xb5\xcd\ 856 \xd4\xda;\xc9\x1a\xc8\xb6\xcd<\xb5\xa0\x85\x1e\xeb\xbc\xbc7b!\xf6\xdeHl\x1c\ 857 \x94\x073\xec<*\xf7\xbe\xf7\x99\x9d\xb21~\xe7.\xf5\x1f\x1c\xd3\xbdVlL\xc2\ 858 \xcf\xf8ye\xd0\x00\x90\x0etH \x84\x80B\xaa\x8a\x88\x85\xc4(U\x9d$\xfeR;\xc5J\ 859 \xa6\x01\xbbt9\xceR\xc8\x81e_$\x98\xb9\x9c\xa9\x8d,y\xa9t\xc8\xcf\x152\xe0x\ 860 \xe9$\xf5\x07\x95\x0cD\x95t:\xb1\x92\xae\x9cI\xa8~\x84\x1f\xe0\xa3ec""" 861
862 -def get_icon(wx=None):
863 864 paths = gmPaths(app_name = u'gnumed', wx = wx) 865 866 candidates = [ 867 os.path.join(paths.system_app_data_dir, 'bitmaps', 'gm_icon-serpent_and_gnu.png'), 868 os.path.join(paths.local_base_dir, 'bitmaps', 'gm_icon-serpent_and_gnu.png'), 869 os.path.join(paths.system_app_data_dir, 'bitmaps', 'serpent.png'), 870 os.path.join(paths.local_base_dir, 'bitmaps', 'serpent.png') 871 ] 872 873 found_as = None 874 for candidate in candidates: 875 try: 876 open(candidate, 'r').close() 877 found_as = candidate 878 break 879 except IOError: 880 _log.debug('icon not found in [%s]', candidate) 881 882 if found_as is None: 883 _log.warning('no icon file found, falling back to builtin (ugly) icon') 884 icon_bmp_data = wx.BitmapFromXPMData(cPickle.loads(zlib.decompress(__icon_serpent))) 885 icon.CopyFromBitmap(icon_bmp_data) 886 else: 887 _log.debug('icon found in [%s]', found_as) 888 icon = wx.EmptyIcon() 889 icon.LoadFile(found_as, wx.BITMAP_TYPE_ANY) #_PNG 890 891 return icon
892 #=========================================================================== 893 # main 894 #--------------------------------------------------------------------------- 895 if __name__ == '__main__': 896 897 if len(sys.argv) < 2: 898 sys.exit() 899 900 if sys.argv[1] != 'test': 901 sys.exit() 902 903 #-----------------------------------------------------------------------
904 - def test_input2decimal():
905 906 tests = [ 907 [None, False], 908 909 ['', False], 910 [' 0 ', True, 0], 911 912 [0, True, 0], 913 [0.0, True, 0], 914 [.0, True, 0], 915 ['0', True, 0], 916 ['0.0', True, 0], 917 ['0,0', True, 0], 918 ['00.0', True, 0], 919 ['.0', True, 0], 920 [',0', True, 0], 921 922 [0.1, True, decimal.Decimal('0.1')], 923 [.01, True, decimal.Decimal('0.01')], 924 ['0.1', True, decimal.Decimal('0.1')], 925 ['0,1', True, decimal.Decimal('0.1')], 926 ['00.1', True, decimal.Decimal('0.1')], 927 ['.1', True, decimal.Decimal('0.1')], 928 [',1', True, decimal.Decimal('0.1')], 929 930 [1, True, 1], 931 [1.0, True, 1], 932 ['1', True, 1], 933 ['1.', True, 1], 934 ['1,', True, 1], 935 ['1.0', True, 1], 936 ['1,0', True, 1], 937 ['01.0', True, 1], 938 ['01,0', True, 1], 939 [' 01, ', True, 1], 940 ] 941 for test in tests: 942 conversion_worked, result = input2decimal(initial = test[0]) 943 944 expected2work = test[1] 945 946 if conversion_worked: 947 if expected2work: 948 if result == test[2]: 949 continue 950 else: 951 print "ERROR (conversion result wrong): >%s<, expected >%s<, got >%s<" % (test[0], test[2], result) 952 else: 953 print "ERROR (conversion worked but was expected to fail): >%s<, got >%s<" % (test[0], result) 954 else: 955 if not expected2work: 956 continue 957 else: 958 print "ERROR (conversion failed but was expected to work): >%s<, expected >%s<" % (test[0], test[2])
959 #-----------------------------------------------------------------------
960 - def test_coalesce():
961 print 'testing coalesce()' 962 print "------------------" 963 tests = [ 964 [None, 'something other than <None>', None, None, 'something other than <None>'], 965 ['Captain', 'Mr.', '%s.'[:4], 'Mr.', 'Capt.'], 966 ['value to test', 'test 3 failed', 'template with "%s" included', None, 'template with "value to test" included'], 967 ['value to test', 'test 4 failed', 'template with value not included', None, 'template with value not included'], 968 [None, 'initial value was None', 'template_initial: %s', None, 'initial value was None'], 969 [None, 'initial value was None', 'template_initial: %%(abc)s', None, 'initial value was None'] 970 ] 971 passed = True 972 for test in tests: 973 result = coalesce ( 974 initial = test[0], 975 instead = test[1], 976 template_initial = test[2], 977 template_instead = test[3] 978 ) 979 if result != test[4]: 980 print "ERROR" 981 print "coalesce: (%s, %s, %s, %s)" % (test[0], test[1], test[2], test[3]) 982 print "expected:", test[4] 983 print "received:", result 984 passed = False 985 986 if passed: 987 print "passed" 988 else: 989 print "failed" 990 return passed
991 #-----------------------------------------------------------------------
992 - def test_capitalize():
993 print 'testing capitalize() ...' 994 success = True 995 pairs = [ 996 # [original, expected result, CAPS mode] 997 [u'Boot', u'Boot', CAPS_FIRST_ONLY], 998 [u'boot', u'Boot', CAPS_FIRST_ONLY], 999 [u'booT', u'Boot', CAPS_FIRST_ONLY], 1000 [u'BoOt', u'Boot', CAPS_FIRST_ONLY], 1001 [u'boots-Schau', u'Boots-Schau', CAPS_WORDS], 1002 [u'boots-sChau', u'Boots-Schau', CAPS_WORDS], 1003 [u'boot camp', u'Boot Camp', CAPS_WORDS], 1004 [u'fahrner-Kampe', u'Fahrner-Kampe', CAPS_NAMES], 1005 [u'häkkönen', u'Häkkönen', CAPS_NAMES], 1006 [u'McBurney', u'McBurney', CAPS_NAMES], 1007 [u'mcBurney', u'McBurney', CAPS_NAMES], 1008 [u'blumberg', u'Blumberg', CAPS_NAMES], 1009 [u'roVsing', u'RoVsing', CAPS_NAMES], 1010 [u'Özdemir', u'Özdemir', CAPS_NAMES], 1011 [u'özdemir', u'Özdemir', CAPS_NAMES], 1012 ] 1013 for pair in pairs: 1014 result = capitalize(pair[0], pair[2]) 1015 if result != pair[1]: 1016 success = False 1017 print 'ERROR (caps mode %s): "%s" -> "%s", expected "%s"' % (pair[2], pair[0], result, pair[1]) 1018 1019 if success: 1020 print "... SUCCESS" 1021 1022 return success
1023 #-----------------------------------------------------------------------
1024 - def test_import_module():
1025 print "testing import_module_from_directory()" 1026 path = sys.argv[1] 1027 name = sys.argv[2] 1028 try: 1029 mod = import_module_from_directory(module_path = path, module_name = name) 1030 except: 1031 print "module import failed, see log" 1032 return False 1033 1034 print "module import succeeded", mod 1035 print dir(mod) 1036 return True
1037 #-----------------------------------------------------------------------
1038 - def test_mkdir():
1039 print "testing mkdir()" 1040 mkdir(sys.argv[1])
1041 #-----------------------------------------------------------------------
1042 - def test_send_mail():
1043 msg = u""" 1044 To: %s 1045 From: %s 1046 Subject: gmTools test suite mail 1047 1048 This is a test mail from the gmTools.py module. 1049 """ % (default_mail_receiver, default_mail_sender) 1050 print "mail sending succeeded:", send_mail ( 1051 receiver = [default_mail_receiver, u'karsten.hilbert@gmx.net'], 1052 message = msg, 1053 auth = {'user': default_mail_sender, 'password': u'gnumed-at-gmx-net'}, # u'gm/bugs/gmx' 1054 debug = True, 1055 attachments = [sys.argv[0]] 1056 )
1057 #-----------------------------------------------------------------------
1058 - def test_gmPaths():
1059 print "testing gmPaths()" 1060 print "-----------------" 1061 paths = gmPaths(wx=None, app_name='gnumed') 1062 print "user config dir:", paths.user_config_dir 1063 print "system config dir:", paths.system_config_dir 1064 print "local base dir:", paths.local_base_dir 1065 print "system app data dir:", paths.system_app_data_dir 1066 print "working directory :", paths.working_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_check_for_update():
1167 1168 test_data = [ 1169 ('http://www.gnumed.de/downloads/gnumed-versions.txt', None, None, False), 1170 ('file:///home/ncq/gm-versions.txt', None, None, False), 1171 ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.1', False), 1172 ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.1', True), 1173 ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.5', True) 1174 ] 1175 1176 for test in test_data: 1177 print "arguments:", test 1178 found, msg = check_for_update(test[0], test[1], test[2], test[3]) 1179 print msg 1180 1181 return
1182 #-----------------------------------------------------------------------
1183 - def test_md5():
1184 print '%s: %s' % (sys.argv[2], file2md5(sys.argv[2]))
1185 #----------------------------------------------------------------------- 1186 #test_check_for_update() 1187 #test_coalesce() 1188 #test_capitalize() 1189 #test_import_module() 1190 #test_mkdir() 1191 #test_send_mail() 1192 test_gmPaths() 1193 #test_none_if() 1194 #test_bool2str() 1195 #test_bool2subst() 1196 #test_get_unique_filename() 1197 #test_size2str() 1198 #test_wrap() 1199 #test_input2decimal() 1200 #test_unwrap() 1201 #test_md5() 1202 1203 #=========================================================================== 1204