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_sum = u'\u2211' 
  55  u_corresponds_to = u'\u2258' 
  56  u_infinity = u'\u221E' 
  57  u_diameter = u'\u2300' 
  58  u_checkmark_crossed_out = u'\u237B' 
  59  u_frowning_face = u'\u2639' 
  60  u_smiling_face = u'\u263a' 
  61  u_black_heart = u'\u2665' 
  62  u_checkmark_thin = u'\u2713' 
  63  u_checkmark_thick = u'\u2714' 
  64  u_writing_hand = u'\u270d' 
  65  u_pencil_1 = u'\u270e' 
  66  u_pencil_2 = u'\u270f' 
  67  u_pencil_3 = u'\u2710' 
  68  u_latin_cross = u'\u271d' 
  69  u_replacement_character = u'\ufffd' 
  70   
  71  #=========================================================================== 
72 -def check_for_update(url=None, current_branch=None, current_version=None, consider_latest_branch=False):
73 """Check for new releases at <url>. 74 75 Returns (bool, text). 76 True: new release available 77 False: up to date 78 None: don't know 79 """ 80 try: 81 remote_file = wget.urlopen(url) 82 except (wget.URLError, ValueError, OSError): 83 _log.exception("cannot retrieve version file from [%s]", url) 84 return (None, _('Cannot retrieve version information from:\n\n%s') % url) 85 86 _log.debug('retrieving version information from [%s]', url) 87 88 from Gnumed.pycommon import gmCfg2 89 cfg = gmCfg2.gmCfgData() 90 try: 91 cfg.add_stream_source(source = 'gm-versions', stream = remote_file) 92 except (UnicodeDecodeError): 93 remote_file.close() 94 _log.exception("cannot read version file from [%s]", url) 95 return (None, _('Cannot read version information from:\n\n%s') % url) 96 97 remote_file.close() 98 99 latest_branch = cfg.get('latest branch', 'branch', source_order = [('gm-versions', 'return')]) 100 latest_release_on_latest_branch = cfg.get('branch %s' % latest_branch, 'latest release', source_order = [('gm-versions', 'return')]) 101 latest_release_on_current_branch = cfg.get('branch %s' % current_branch, 'latest release', source_order = [('gm-versions', 'return')]) 102 103 cfg.remove_source('gm-versions') 104 105 _log.info('current release: %s', current_version) 106 _log.info('current branch: %s', current_branch) 107 _log.info('latest release on current branch: %s', latest_release_on_current_branch) 108 _log.info('latest branch: %s', latest_branch) 109 _log.info('latest release on latest branch: %s', latest_release_on_latest_branch) 110 111 # anything known ? 112 no_release_information_available = ( 113 ( 114 (latest_release_on_current_branch is None) and 115 (latest_release_on_latest_branch is None) 116 ) or ( 117 not consider_latest_branch and 118 (latest_release_on_current_branch is None) 119 ) 120 ) 121 if no_release_information_available: 122 _log.warning('no release information available') 123 msg = _('There is no version information available from:\n\n%s') % url 124 return (None, msg) 125 126 # up to date ? 127 if consider_latest_branch: 128 _log.debug('latest branch taken into account') 129 if current_version >= latest_release_on_latest_branch: 130 _log.debug('up to date: current version >= latest version on latest branch') 131 return (False, None) 132 if latest_release_on_latest_branch is None: 133 if current_version >= latest_release_on_current_branch: 134 _log.debug('up to date: current version >= latest version on current branch and no latest branch available') 135 return (False, None) 136 else: 137 _log.debug('latest branch not taken into account') 138 if current_version >= latest_release_on_current_branch: 139 _log.debug('up to date: current version >= latest version on current branch') 140 return (False, None) 141 142 new_release_on_current_branch_available = ( 143 (latest_release_on_current_branch is not None) and 144 (latest_release_on_current_branch > current_version) 145 ) 146 _log.info('%snew release on current branch available', bool2str(new_release_on_current_branch_available, '', 'no ')) 147 148 new_release_on_latest_branch_available = ( 149 (latest_branch is not None) 150 and 151 ( 152 (latest_branch > current_branch) or ( 153 (latest_branch == current_branch) and 154 (latest_release_on_latest_branch > current_version) 155 ) 156 ) 157 ) 158 _log.info('%snew release on latest branch available', bool2str(new_release_on_latest_branch_available, '', 'no ')) 159 160 if not (new_release_on_current_branch_available or new_release_on_latest_branch_available): 161 _log.debug('up to date: no new releases available') 162 return (False, None) 163 164 # not up to date 165 msg = _('A new version of GNUmed is available.\n\n') 166 msg += _(' Your current version: "%s"\n') % current_version 167 if consider_latest_branch: 168 if new_release_on_current_branch_available: 169 msg += u'\n' 170 msg += _(' New version: "%s"') % latest_release_on_current_branch 171 msg += u'\n' 172 msg += _(' - bug fixes only\n') 173 msg += _(' - database fixups may be needed\n') 174 if new_release_on_latest_branch_available: 175 if current_branch != latest_branch: 176 msg += u'\n' 177 msg += _(' New version: "%s"') % latest_release_on_latest_branch 178 msg += u'\n' 179 msg += _(' - bug fixes and new features\n') 180 msg += _(' - database upgrade required\n') 181 else: 182 msg += u'\n' 183 msg += _(' New version: "%s"') % latest_release_on_current_branch 184 msg += u'\n' 185 msg += _(' - bug fixes only\n') 186 msg += _(' - database fixups may be needed\n') 187 188 msg += u'\n\n' 189 msg += _( 190 'Note, however, that this version may not yet\n' 191 'be available *pre-packaged* for your system.' 192 ) 193 194 msg += u'\n\n' 195 msg += _('Details are found on <http://wiki.gnumed.de>.\n') 196 msg += u'\n' 197 msg += _('Version information loaded from:\n\n %s') % url 198 199 return (True, msg)
200 #===========================================================================
201 -def handle_uncaught_exception_console(t, v, tb):
202 203 print ".========================================================" 204 print "| Unhandled exception caught !" 205 print "| Type :", t 206 print "| Value:", v 207 print "`========================================================" 208 _log.critical('unhandled exception caught', exc_info = (t,v,tb)) 209 sys.__excepthook__(t,v,tb)
210 #=========================================================================== 211 # path level operations 212 #---------------------------------------------------------------------------
213 -def mkdir(directory=None):
214 try: 215 os.makedirs(directory) 216 except OSError, e: 217 if (e.errno == 17) and not os.path.isdir(directory): 218 raise 219 return True
220 221 #---------------------------------------------------------------------------
222 -class gmPaths(gmBorg.cBorg):
223 """This class provides the following paths: 224 225 .home_dir 226 .local_base_dir 227 .working_dir 228 .user_config_dir 229 .system_config_dir 230 .system_app_data_dir 231 """
232 - def __init__(self, app_name=None, wx=None):
233 """Setup pathes. 234 235 <app_name> will default to (name of the script - .py) 236 """ 237 try: 238 self.already_inited 239 return 240 except AttributeError: 241 pass 242 243 self.init_paths(app_name=app_name, wx=wx) 244 self.already_inited = True
245 #-------------------------------------- 246 # public API 247 #--------------------------------------
248 - def init_paths(self, app_name=None, wx=None):
249 250 if wx is None: 251 _log.debug('wxPython not available') 252 _log.debug('detecting paths directly') 253 254 if app_name is None: 255 app_name, ext = os.path.splitext(os.path.basename(sys.argv[0])) 256 _log.info('app name detected as [%s]', app_name) 257 else: 258 _log.info('app name passed in as [%s]', app_name) 259 260 # the user home, doesn't work in Wine so work around that 261 self.__home_dir = None 262 263 # where the main script (the "binary") is installed 264 #self.local_base_dir = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '..', '.')) 265 self.local_base_dir = os.path.abspath(os.path.dirname(sys.argv[0])) 266 267 # the current working dir at the OS 268 self.working_dir = os.path.abspath(os.curdir) 269 270 # user-specific config dir, usually below the home dir 271 #mkdir(os.path.expanduser(os.path.join('~', '.%s' % app_name))) 272 #self.user_config_dir = os.path.expanduser(os.path.join('~', '.%s' % app_name)) 273 mkdir(os.path.join(self.home_dir, '.%s' % app_name)) 274 self.user_config_dir = os.path.join(self.home_dir, '.%s' % app_name) 275 276 # system-wide config dir, usually below /etc/ under UN*X 277 try: 278 self.system_config_dir = os.path.join('/etc', app_name) 279 except ValueError: 280 #self.system_config_dir = self.local_base_dir 281 self.system_config_dir = self.user_config_dir 282 283 # system-wide application data dir 284 try: 285 self.system_app_data_dir = os.path.join(sys.prefix, 'share', app_name) 286 except ValueError: 287 self.system_app_data_dir = self.local_base_dir 288 289 self.__log_paths() 290 if wx is None: 291 return True 292 293 # retry with wxPython 294 _log.debug('re-detecting paths with wxPython') 295 296 std_paths = wx.StandardPaths.Get() 297 _log.info('wxPython app name is [%s]', wx.GetApp().GetAppName()) 298 299 # user-specific config dir, usually below the home dir 300 mkdir(os.path.join(std_paths.GetUserConfigDir(), '.%s' % app_name)) 301 self.user_config_dir = os.path.join(std_paths.GetUserConfigDir(), '.%s' % app_name) 302 303 # system-wide config dir, usually below /etc/ under UN*X 304 try: 305 tmp = std_paths.GetConfigDir() 306 if not tmp.endswith(app_name): 307 tmp = os.path.join(tmp, app_name) 308 self.system_config_dir = tmp 309 except ValueError: 310 # leave it at what it was from direct detection 311 pass 312 313 # system-wide application data dir 314 # Robin attests that the following doesn't always 315 # give sane values on Windows, so IFDEF it 316 if 'wxMSW' in wx.PlatformInfo: 317 _log.warning('this platform (wxMSW) sometimes returns a broken value for the system-wide application data dir') 318 else: 319 try: 320 self.system_app_data_dir = std_paths.GetDataDir() 321 except ValueError: 322 pass 323 324 self.__log_paths() 325 return True
326 #--------------------------------------
327 - def __log_paths(self):
328 _log.debug('sys.argv[0]: %s', sys.argv[0]) 329 _log.debug('local application base dir: %s', self.local_base_dir) 330 _log.debug('current working dir: %s', self.working_dir) 331 #_log.debug('user home dir: %s', os.path.expanduser('~')) 332 _log.debug('user home dir: %s', self.home_dir) 333 _log.debug('user-specific config dir: %s', self.user_config_dir) 334 _log.debug('system-wide config dir: %s', self.system_config_dir) 335 _log.debug('system-wide application data dir: %s', self.system_app_data_dir)
336 #-------------------------------------- 337 # properties 338 #--------------------------------------
339 - def _set_user_config_dir(self, path):
340 if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)): 341 msg = '[%s:user_config_dir]: invalid path [%s]' % (self.__class__.__name__, path) 342 _log.error(msg) 343 raise ValueError(msg) 344 self.__user_config_dir = path
345
346 - def _get_user_config_dir(self):
347 return self.__user_config_dir
348 349 user_config_dir = property(_get_user_config_dir, _set_user_config_dir) 350 #--------------------------------------
351 - def _set_system_config_dir(self, path):
352 if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)): 353 msg = '[%s:system_config_dir]: invalid path [%s]' % (self.__class__.__name__, path) 354 _log.error(msg) 355 raise ValueError(msg) 356 self.__system_config_dir = path
357
358 - def _get_system_config_dir(self):
359 return self.__system_config_dir
360 361 system_config_dir = property(_get_system_config_dir, _set_system_config_dir) 362 #--------------------------------------
363 - def _set_system_app_data_dir(self, path):
364 if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)): 365 msg = '[%s:system_app_data_dir]: invalid path [%s]' % (self.__class__.__name__, path) 366 _log.error(msg) 367 raise ValueError(msg) 368 self.__system_app_data_dir = path
369
370 - def _get_system_app_data_dir(self):
371 return self.__system_app_data_dir
372 373 system_app_data_dir = property(_get_system_app_data_dir, _set_system_app_data_dir) 374 #--------------------------------------
375 - def _set_home_dir(self, path):
376 raise ArgumentError('invalid to set home dir')
377
378 - def _get_home_dir(self):
379 if self.__home_dir is not None: 380 return self.__home_dir 381 382 tmp = os.path.expanduser('~') 383 if tmp == '~': 384 _log.error('this platform does not expand ~ properly') 385 try: 386 tmp = os.environ['USERPROFILE'] 387 except KeyError: 388 _log.error('cannot access $USERPROFILE in environment') 389 390 if not ( 391 os.access(tmp, os.R_OK) 392 and 393 os.access(tmp, os.X_OK) 394 and 395 os.access(tmp, os.W_OK) 396 ): 397 msg = '[%s:home_dir]: invalid path [%s]' % (self.__class__.__name__, tmp) 398 _log.error(msg) 399 raise ValueError(msg) 400 401 self.__home_dir = tmp 402 return self.__home_dir
403 404 home_dir = property(_get_home_dir, _set_home_dir)
405 #===========================================================================
406 -def send_mail(sender=None, receiver=None, message=None, server=None, auth=None, debug=False, subject=None, encoding='quoted-printable', attachments=None):
407 408 if message is None: 409 return False 410 411 message = message.lstrip().lstrip('\r\n').lstrip() 412 413 if sender is None: 414 sender = default_mail_sender 415 416 if receiver is None: 417 receiver = [default_mail_receiver] 418 419 if server is None: 420 server = default_mail_server 421 422 if subject is None: 423 subject = u'gmTools.py: send_mail() test' 424 425 msg = StringIO.StringIO() 426 writer = MimeWriter.MimeWriter(msg) 427 writer.addheader('To', u', '.join(receiver)) 428 writer.addheader('From', sender) 429 writer.addheader('Subject', subject[:50].replace('\r', '/').replace('\n', '/')) 430 writer.addheader('MIME-Version', '1.0') 431 432 writer.startmultipartbody('mixed') 433 434 # start with a text/plain part 435 part = writer.nextpart() 436 body = part.startbody('text/plain') 437 part.flushheaders() 438 body.write(message.encode(encoding)) 439 440 # now add the attachments 441 if attachments is not None: 442 for a in attachments: 443 filename = os.path.basename(a[0]) 444 try: 445 mtype = a[1] 446 encoding = a[2] 447 except IndexError: 448 mtype, encoding = mimetypes.guess_type(a[0]) 449 if mtype is None: 450 mtype = 'application/octet-stream' 451 encoding = 'base64' 452 elif mtype == 'text/plain': 453 encoding = 'quoted-printable' 454 else: 455 encoding = 'base64' 456 457 part = writer.nextpart() 458 part.addheader('Content-Transfer-Encoding', encoding) 459 body = part.startbody("%s; name=%s" % (mtype, filename)) 460 mimetools.encode(open(a[0], 'rb'), body, encoding) 461 462 writer.lastpart() 463 464 import smtplib 465 session = smtplib.SMTP(server) 466 session.set_debuglevel(debug) 467 if auth is not None: 468 session.login(auth['user'], auth['password']) 469 refused = session.sendmail(sender, receiver, msg.getvalue()) 470 session.quit() 471 msg.close() 472 if len(refused) != 0: 473 _log.error("refused recipients: %s" % refused) 474 return False 475 476 return True
477 #-------------------------------------------------------------------------------
478 -def send_mail_old(sender=None, receiver=None, message=None, server=None, auth=None, debug=False, subject=None, encoding='latin1'):
479 """Send an E-Mail. 480 481 <debug>: see smtplib.set_debuglevel() 482 <auth>: {'user': ..., 'password': ...} 483 <receiver>: a list of email addresses 484 """ 485 if message is None: 486 return False 487 message = message.lstrip().lstrip('\r\n').lstrip() 488 489 if sender is None: 490 sender = default_mail_sender 491 492 if receiver is None: 493 receiver = [default_mail_receiver] 494 495 if server is None: 496 server = default_mail_server 497 498 if subject is None: 499 subject = u'gmTools.py: send_mail() test' 500 501 body = u"""From: %s 502 To: %s 503 Subject: %s 504 505 %s 506 """ % (sender, u', '.join(receiver), subject[:50].replace('\r', '/').replace('\n', '/'), message) 507 508 import smtplib 509 session = smtplib.SMTP(server) 510 session.set_debuglevel(debug) 511 if auth is not None: 512 session.login(auth['user'], auth['password']) 513 refused = session.sendmail(sender, receiver, body.encode(encoding)) 514 session.quit() 515 if len(refused) != 0: 516 _log.error("refused recipients: %s" % refused) 517 return False 518 519 return True
520 #=========================================================================== 521 # file related tools 522 #---------------------------------------------------------------------------
523 -def file2md5(filename=None, return_hex=True):
524 blocksize = 2**10 * 128 # 128k, since md5 use 128 byte blocks 525 _log.debug('md5(%s): <%s> byte blocks', filename, blocksize) 526 527 f = open(filename, 'rb') 528 529 md5 = hashlib.md5() 530 while True: 531 data = f.read(blocksize) 532 if not data: 533 break 534 md5.update(data) 535 536 _log.debug('md5(%s): %s', filename, md5.hexdigest()) 537 538 if return_hex: 539 return md5.hexdigest() 540 return md5.digest()
541 #---------------------------------------------------------------------------
542 -def unicode2charset_encoder(unicode_csv_data, encoding='utf-8'):
543 for line in unicode_csv_data: 544 yield line.encode(encoding)
545 546 #def utf_8_encoder(unicode_csv_data): 547 # for line in unicode_csv_data: 548 # yield line.encode('utf-8') 549
550 -def unicode_csv_reader(unicode_csv_data, dialect=csv.excel, encoding='utf-8', **kwargs):
551 # csv.py doesn't do Unicode; encode temporarily as UTF-8: 552 try: 553 is_dict_reader = kwargs['dict'] 554 del kwargs['dict'] 555 if is_dict_reader is not True: 556 raise KeyError 557 csv_reader = csv.DictReader(unicode2charset_encoder(unicode_csv_data), dialect=dialect, **kwargs) 558 except KeyError: 559 is_dict_reader = False 560 csv_reader = csv.reader(unicode2charset_encoder(unicode_csv_data), dialect=dialect, **kwargs) 561 562 for row in csv_reader: 563 # decode ENCODING back to Unicode, cell by cell: 564 if is_dict_reader: 565 for key in row.keys(): 566 row[key] = unicode(row[key], encoding) 567 yield row 568 else: 569 yield [ unicode(cell, encoding) for cell in row ]
570 #yield [unicode(cell, 'utf-8') for cell in row] 571 #---------------------------------------------------------------------------
572 -def get_unique_filename(prefix=None, suffix=None, tmp_dir=None):
573 """This introduces a race condition between the file.close() and 574 actually using the filename. 575 576 The file will not exist after calling this function. 577 """ 578 if tmp_dir is not None: 579 if ( 580 not os.access(tmp_dir, os.F_OK) 581 or 582 not os.access(tmp_dir, os.X_OK | os.W_OK) 583 ): 584 _log.info('cannot find temporary dir [%s], using system default', tmp_dir) 585 tmp_dir = None 586 587 kwargs = {'dir': tmp_dir} 588 589 if prefix is None: 590 kwargs['prefix'] = 'gnumed-' 591 else: 592 kwargs['prefix'] = prefix 593 594 if suffix in [None, u'']: 595 kwargs['suffix'] = '.tmp' 596 else: 597 if not suffix.startswith('.'): 598 suffix = '.' + suffix 599 kwargs['suffix'] = suffix 600 601 f = tempfile.NamedTemporaryFile(**kwargs) 602 filename = f.name 603 f.close() 604 605 return filename
606 #===========================================================================
607 -def import_module_from_directory(module_path=None, module_name=None, always_remove_path=False):
608 """Import a module from any location.""" 609 610 remove_path = always_remove_path or False 611 if module_path not in sys.path: 612 _log.info('appending to sys.path: [%s]' % module_path) 613 sys.path.append(module_path) 614 remove_path = True 615 616 _log.debug('will remove import path: %s', remove_path) 617 618 if module_name.endswith('.py'): 619 module_name = module_name[:-3] 620 621 try: 622 module = __import__(module_name) 623 except StandardError: 624 _log.exception('cannot __import__() module [%s] from [%s]' % (module_name, module_path)) 625 while module_path in sys.path: 626 sys.path.remove(module_path) 627 raise 628 629 _log.info('imported module [%s] as [%s]' % (module_name, module)) 630 if remove_path: 631 while module_path in sys.path: 632 sys.path.remove(module_path) 633 634 return module
635 #=========================================================================== 636 # text related tools 637 #--------------------------------------------------------------------------- 638 _kB = 1024 639 _MB = 1024 * _kB 640 _GB = 1024 * _MB 641 _TB = 1024 * _GB 642 _PB = 1024 * _TB 643 #---------------------------------------------------------------------------
644 -def size2str(size=0, template='%s'):
645 if size == 1: 646 return template % _('1 Byte') 647 if size < 10 * _kB: 648 return template % _('%s Bytes') % size 649 if size < _MB: 650 return template % u'%.1f kB' % (float(size) / _kB) 651 if size < _GB: 652 return template % u'%.1f MB' % (float(size) / _MB) 653 if size < _TB: 654 return template % u'%.1f GB' % (float(size) / _GB) 655 if size < _PB: 656 return template % u'%.1f TB' % (float(size) / _TB) 657 return template % u'%.1f PB' % (float(size) / _PB)
658 #---------------------------------------------------------------------------
659 -def bool2subst(boolean=None, true_return=True, false_return=False, none_return=None):
660 if boolean is None: 661 return none_return 662 if boolean is True: 663 return true_return 664 if boolean is False: 665 return false_return 666 raise ValueError('bool2subst(): <boolean> arg must be either of True, False, None')
667 #---------------------------------------------------------------------------
668 -def bool2str(boolean=None, true_str='True', false_str='False'):
669 return bool2subst ( 670 boolean = bool(boolean), 671 true_return = true_str, 672 false_return = false_str 673 )
674 #---------------------------------------------------------------------------
675 -def none_if(value=None, none_equivalent=None):
676 """Modelled after the SQL NULLIF function.""" 677 if value == none_equivalent: 678 return None 679 return value
680 #---------------------------------------------------------------------------
681 -def coalesce(initial=None, instead=None, template_initial=None, template_instead=None, none_equivalents=None):
682 """Modelled after the SQL coalesce function. 683 684 To be used to simplify constructs like: 685 686 if initial is None (or in none_equivalents): 687 real_value = (template_instead % instead) or instead 688 else: 689 real_value = (template_initial % initial) or initial 690 print real_value 691 692 @param initial: the value to be tested for <None> 693 @type initial: any Python type, must have a __str__ method if template_initial is not None 694 @param instead: the value to be returned if <initial> is None 695 @type instead: any Python type, must have a __str__ method if template_instead is not None 696 @param template_initial: if <initial> is returned replace the value into this template, must contain one <%s> 697 @type template_initial: string or None 698 @param template_instead: if <instead> is returned replace the value into this template, must contain one <%s> 699 @type template_instead: string or None 700 701 Ideas: 702 - list of insteads: initial, [instead, template], [instead, template], [instead, template], template_initial, ... 703 """ 704 if none_equivalents is None: 705 none_equivalents = [None] 706 707 if initial in none_equivalents: 708 709 if template_instead is None: 710 return instead 711 712 return template_instead % instead 713 714 if template_initial is None: 715 return initial 716 717 try: 718 return template_initial % initial 719 except TypeError: 720 return template_initial
721 #---------------------------------------------------------------------------
722 -def __cap_name(match_obj=None):
723 val = match_obj.group(0).lower() 724 if val in ['von', 'van', 'de', 'la', 'l', 'der', 'den']: # FIXME: this needs to expand, configurable ? 725 return val 726 buf = list(val) 727 buf[0] = buf[0].upper() 728 for part in ['mac', 'mc', 'de', 'la']: 729 if len(val) > len(part) and val[:len(part)] == part: 730 buf[len(part)] = buf[len(part)].upper() 731 return ''.join(buf)
732 #---------------------------------------------------------------------------
733 -def capitalize(text=None, mode=CAPS_NAMES):
734 """Capitalize the first character but leave the rest alone. 735 736 Note that we must be careful about the locale, this may 737 have issues ! However, for UTF strings it should just work. 738 """ 739 if (mode is None) or (mode == CAPS_NONE): 740 return text 741 742 if mode == CAPS_FIRST: 743 if len(text) == 1: 744 return text[0].upper() 745 return text[0].upper() + text[1:] 746 747 if mode == CAPS_ALLCAPS: 748 return text.upper() 749 750 if mode == CAPS_FIRST_ONLY: 751 if len(text) == 1: 752 return text[0].upper() 753 return text[0].upper() + text[1:].lower() 754 755 if mode == CAPS_WORDS: 756 return regex.sub(ur'(\w)(\w+)', lambda x: x.group(1).upper() + x.group(2).lower(), text) 757 758 if mode == CAPS_NAMES: 759 #return regex.sub(r'\w+', __cap_name, text) 760 return capitalize(text=text, mode=CAPS_FIRST) # until fixed 761 762 print "ERROR: invalid capitalization mode: [%s], leaving input as is" % mode 763 return text
764 #---------------------------------------------------------------------------
765 -def input2decimal(initial=None):
766 767 val = initial 768 769 # float ? -> to string first 770 if type(val) == type(1.4): 771 val = str(val) 772 773 # string ? -> "," to "." 774 if isinstance(val, basestring): 775 val = val.replace(',', '.', 1) 776 val = val.strip() 777 # val = val.lstrip('0') 778 # if val.startswith('.'): 779 # val = '0' + val 780 781 try: 782 d = decimal.Decimal(val) 783 return True, d 784 except (TypeError, decimal.InvalidOperation): 785 return False, val
786 #---------------------------------------------------------------------------
787 -def wrap(text=None, width=None, initial_indent=u'', subsequent_indent=u'', eol=u'\n'):
788 """A word-wrap function that preserves existing line breaks 789 and most spaces in the text. Expects that existing line 790 breaks are posix newlines (\n). 791 """ 792 wrapped = initial_indent + reduce ( 793 lambda line, word, width=width: '%s%s%s' % ( 794 line, 795 ' \n'[(len(line) - line.rfind('\n') - 1 + len(word.split('\n',1)[0]) >= width)], 796 word 797 ), 798 text.split(' ') 799 ) 800 801 if subsequent_indent != u'': 802 wrapped = (u'\n%s' % subsequent_indent).join(wrapped.split('\n')) 803 804 if eol != u'\n': 805 wrapped = wrapped.replace('\n', eol) 806 807 return wrapped
808 #---------------------------------------------------------------------------
809 -def unwrap(text=None, max_length=None, strip_whitespace=True, remove_empty_lines=True, line_separator = u' // '):
810 811 text = text.replace(u'\r', u'') 812 lines = text.split(u'\n') 813 text = u'' 814 for line in lines: 815 816 if strip_whitespace: 817 line = line.strip().strip(u'\t').strip() 818 819 if remove_empty_lines: 820 if line == u'': 821 continue 822 823 text += (u'%s%s' % (line, line_separator)) 824 825 text = text.rstrip(line_separator) 826 827 if max_length is not None: 828 text = text[:max_length] 829 830 text = text.rstrip(line_separator) 831 832 return text
833 #---------------------------------------------------------------------------
834 -def tex_escape_string(text=None):
835 """check for special latex-characters and transform them""" 836 837 text = text.replace(u'\\', u'$\\backslash$') 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 text = text.replace(u'_', u'\\_') 845 846 text = text.replace(u'^', u'\\verb#^#') 847 text = text.replace('~','\\verb#~#') 848 849 return text
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 for test in tests: 946 conversion_worked, result = input2decimal(initial = test[0]) 947 948 expected2work = test[1] 949 950 if conversion_worked: 951 if expected2work: 952 if result == test[2]: 953 continue 954 else: 955 print "ERROR (conversion result wrong): >%s<, expected >%s<, got >%s<" % (test[0], test[2], result) 956 else: 957 print "ERROR (conversion worked but was expected to fail): >%s<, got >%s<" % (test[0], result) 958 else: 959 if not expected2work: 960 continue 961 else: 962 print "ERROR (conversion failed but was expected to work): >%s<, expected >%s<" % (test[0], test[2])
963 #-----------------------------------------------------------------------
964 - def test_coalesce():
965 print 'testing coalesce()' 966 print "------------------" 967 tests = [ 968 [None, 'something other than <None>', None, None, 'something other than <None>'], 969 ['Captain', 'Mr.', '%s.'[:4], 'Mr.', 'Capt.'], 970 ['value to test', 'test 3 failed', 'template with "%s" included', None, 'template with "value to test" included'], 971 ['value to test', 'test 4 failed', 'template with value not included', None, 'template with value not included'], 972 [None, 'initial value was None', 'template_initial: %s', None, 'initial value was None'], 973 [None, 'initial value was None', 'template_initial: %%(abc)s', None, 'initial value was None'] 974 ] 975 passed = True 976 for test in tests: 977 result = coalesce ( 978 initial = test[0], 979 instead = test[1], 980 template_initial = test[2], 981 template_instead = test[3] 982 ) 983 if result != test[4]: 984 print "ERROR" 985 print "coalesce: (%s, %s, %s, %s)" % (test[0], test[1], test[2], test[3]) 986 print "expected:", test[4] 987 print "received:", result 988 passed = False 989 990 if passed: 991 print "passed" 992 else: 993 print "failed" 994 return passed
995 #-----------------------------------------------------------------------
996 - def test_capitalize():
997 print 'testing capitalize() ...' 998 success = True 999 pairs = [ 1000 # [original, expected result, CAPS mode] 1001 [u'Boot', u'Boot', CAPS_FIRST_ONLY], 1002 [u'boot', u'Boot', CAPS_FIRST_ONLY], 1003 [u'booT', u'Boot', CAPS_FIRST_ONLY], 1004 [u'BoOt', u'Boot', CAPS_FIRST_ONLY], 1005 [u'boots-Schau', u'Boots-Schau', CAPS_WORDS], 1006 [u'boots-sChau', u'Boots-Schau', CAPS_WORDS], 1007 [u'boot camp', u'Boot Camp', CAPS_WORDS], 1008 [u'fahrner-Kampe', u'Fahrner-Kampe', CAPS_NAMES], 1009 [u'häkkönen', u'Häkkönen', CAPS_NAMES], 1010 [u'McBurney', u'McBurney', CAPS_NAMES], 1011 [u'mcBurney', u'McBurney', CAPS_NAMES], 1012 [u'blumberg', u'Blumberg', CAPS_NAMES], 1013 [u'roVsing', u'RoVsing', CAPS_NAMES], 1014 [u'Özdemir', u'Özdemir', CAPS_NAMES], 1015 [u'özdemir', u'Özdemir', CAPS_NAMES], 1016 ] 1017 for pair in pairs: 1018 result = capitalize(pair[0], pair[2]) 1019 if result != pair[1]: 1020 success = False 1021 print 'ERROR (caps mode %s): "%s" -> "%s", expected "%s"' % (pair[2], pair[0], result, pair[1]) 1022 1023 if success: 1024 print "... SUCCESS" 1025 1026 return success
1027 #-----------------------------------------------------------------------
1028 - def test_import_module():
1029 print "testing import_module_from_directory()" 1030 path = sys.argv[1] 1031 name = sys.argv[2] 1032 try: 1033 mod = import_module_from_directory(module_path = path, module_name = name) 1034 except: 1035 print "module import failed, see log" 1036 return False 1037 1038 print "module import succeeded", mod 1039 print dir(mod) 1040 return True
1041 #-----------------------------------------------------------------------
1042 - def test_mkdir():
1043 print "testing mkdir()" 1044 mkdir(sys.argv[1])
1045 #-----------------------------------------------------------------------
1046 - def test_send_mail():
1047 msg = u""" 1048 To: %s 1049 From: %s 1050 Subject: gmTools test suite mail 1051 1052 This is a test mail from the gmTools.py module. 1053 """ % (default_mail_receiver, default_mail_sender) 1054 print "mail sending succeeded:", send_mail ( 1055 receiver = [default_mail_receiver, u'karsten.hilbert@gmx.net'], 1056 message = msg, 1057 auth = {'user': default_mail_sender, 'password': u'gnumed-at-gmx-net'}, # u'gm/bugs/gmx' 1058 debug = True, 1059 attachments = [sys.argv[0]] 1060 )
1061 #-----------------------------------------------------------------------
1062 - def test_gmPaths():
1063 print "testing gmPaths()" 1064 print "-----------------" 1065 paths = gmPaths(wx=None, app_name='gnumed') 1066 print "user config dir:", paths.user_config_dir 1067 print "system config dir:", paths.system_config_dir 1068 print "local base dir:", paths.local_base_dir 1069 print "system app data dir:", paths.system_app_data_dir 1070 print "working directory :", paths.working_dir
1071 #-----------------------------------------------------------------------
1072 - def test_none_if():
1073 print "testing none_if()" 1074 print "-----------------" 1075 tests = [ 1076 [None, None, None], 1077 ['a', 'a', None], 1078 ['a', 'b', 'a'], 1079 ['a', None, 'a'], 1080 [None, 'a', None], 1081 [1, 1, None], 1082 [1, 2, 1], 1083 [1, None, 1], 1084 [None, 1, None] 1085 ] 1086 1087 for test in tests: 1088 if none_if(value = test[0], none_equivalent = test[1]) != test[2]: 1089 print 'ERROR: none_if(%s) returned [%s], expected [%s]' % (test[0], none_if(test[0], test[1]), test[2]) 1090 1091 return True
1092 #-----------------------------------------------------------------------
1093 - def test_bool2str():
1094 tests = [ 1095 [True, 'Yes', 'Yes', 'Yes'], 1096 [False, 'OK', 'not OK', 'not OK'] 1097 ] 1098 for test in tests: 1099 if bool2str(test[0], test[1], test[2]) != test[3]: 1100 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]) 1101 1102 return True
1103 #-----------------------------------------------------------------------
1104 - def test_bool2subst():
1105 1106 print bool2subst(True, 'True', 'False', 'is None') 1107 print bool2subst(False, 'True', 'False', 'is None') 1108 print bool2subst(None, 'True', 'False', 'is None')
1109 #-----------------------------------------------------------------------
1110 - def test_get_unique_filename():
1111 print get_unique_filename() 1112 print get_unique_filename(prefix='test-') 1113 print get_unique_filename(suffix='tst') 1114 print get_unique_filename(prefix='test-', suffix='tst') 1115 print get_unique_filename(tmp_dir='/home/ncq/Archiv/')
1116 #-----------------------------------------------------------------------
1117 - def test_size2str():
1118 print "testing size2str()" 1119 print "------------------" 1120 tests = [0, 1, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000, 1000000000000, 10000000000000] 1121 for test in tests: 1122 print size2str(test)
1123 #-----------------------------------------------------------------------
1124 - def test_unwrap():
1125 1126 test = """ 1127 second line\n 1128 3rd starts with tab \n 1129 4th with a space \n 1130 1131 6th 1132 1133 """ 1134 print unwrap(text = test, max_length = 25)
1135 #-----------------------------------------------------------------------
1136 - def test_wrap():
1137 test = 'line 1\nline 2\nline 3' 1138 1139 print "wrap 5-6-7 initial 0, subsequent 0" 1140 print wrap(test, 5) 1141 print 1142 print wrap(test, 6) 1143 print 1144 print wrap(test, 7) 1145 print "-------" 1146 raw_input() 1147 print "wrap 5 initial 1-1-3, subsequent 1-3-1" 1148 print wrap(test, 5, u' ', u' ') 1149 print 1150 print wrap(test, 5, u' ', u' ') 1151 print 1152 print wrap(test, 5, u' ', u' ') 1153 print "-------" 1154 raw_input() 1155 print "wrap 6 initial 1-1-3, subsequent 1-3-1" 1156 print wrap(test, 6, u' ', u' ') 1157 print 1158 print wrap(test, 6, u' ', u' ') 1159 print 1160 print wrap(test, 6, u' ', u' ') 1161 print "-------" 1162 raw_input() 1163 print "wrap 7 initial 1-1-3, subsequent 1-3-1" 1164 print wrap(test, 7, u' ', u' ') 1165 print 1166 print wrap(test, 7, u' ', u' ') 1167 print 1168 print wrap(test, 7, u' ', u' ')
1169 #-----------------------------------------------------------------------
1170 - def test_check_for_update():
1171 1172 test_data = [ 1173 ('http://www.gnumed.de/downloads/gnumed-versions.txt', None, None, False), 1174 ('file:///home/ncq/gm-versions.txt', None, None, False), 1175 ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.1', False), 1176 ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.1', True), 1177 ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.5', True) 1178 ] 1179 1180 for test in test_data: 1181 print "arguments:", test 1182 found, msg = check_for_update(test[0], test[1], test[2], test[3]) 1183 print msg 1184 1185 return
1186 #-----------------------------------------------------------------------
1187 - def test_md5():
1188 print '%s: %s' % (sys.argv[2], file2md5(sys.argv[2]))
1189 #----------------------------------------------------------------------- 1190 #test_check_for_update() 1191 #test_coalesce() 1192 #test_capitalize() 1193 #test_import_module() 1194 #test_mkdir() 1195 #test_send_mail() 1196 test_gmPaths() 1197 #test_none_if() 1198 #test_bool2str() 1199 #test_bool2subst() 1200 #test_get_unique_filename() 1201 #test_size2str() 1202 #test_wrap() 1203 #test_input2decimal() 1204 #test_unwrap() 1205 #test_md5() 1206 1207 #=========================================================================== 1208