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 -def prompted_input(prompt=None, default=None):
852 """Obtains entry from standard input. 853 854 prompt: Prompt text to display in standard output 855 default: Default value (for user to press enter only) 856 CTRL-C: aborts and returns None 857 """ 858 if prompt is None: 859 msg = u'(CTRL-C aborts)' 860 else: 861 msg = u'%s (CTRL-C aborts)' % prompt 862 863 if default is None: 864 msg = msg + u': ' 865 else: 866 msg = u'%s [%s]: ' % (msg, default) 867 868 try: 869 usr_input = raw_input(msg) 870 except KeyboardInterrupt: 871 return None 872 873 if usr_input == '': 874 return default 875 876 return usr_input
877 878 #=========================================================================== 879 # image handling tools 880 #--------------------------------------------------------------------------- 881 # builtin (ugly but tried and true) fallback icon 882 __icon_serpent = \ 883 """x\xdae\x8f\xb1\x0e\x83 \x10\x86w\x9f\xe2\x92\x1blb\xf2\x07\x96\xeaH:0\xd6\ 884 \xc1\x85\xd5\x98N5\xa5\xef?\xf5N\xd0\x8a\xdcA\xc2\xf7qw\x84\xdb\xfa\xb5\xcd\ 885 \xd4\xda;\xc9\x1a\xc8\xb6\xcd<\xb5\xa0\x85\x1e\xeb\xbc\xbc7b!\xf6\xdeHl\x1c\ 886 \x94\x073\xec<*\xf7\xbe\xf7\x99\x9d\xb21~\xe7.\xf5\x1f\x1c\xd3\xbdVlL\xc2\ 887 \xcf\xf8ye\xd0\x00\x90\x0etH \x84\x80B\xaa\x8a\x88\x85\xc4(U\x9d$\xfeR;\xc5J\ 888 \xa6\x01\xbbt9\xceR\xc8\x81e_$\x98\xb9\x9c\xa9\x8d,y\xa9t\xc8\xcf\x152\xe0x\ 889 \xe9$\xf5\x07\x95\x0cD\x95t:\xb1\x92\xae\x9cI\xa8~\x84\x1f\xe0\xa3ec""" 890
891 -def get_icon(wx=None):
892 893 paths = gmPaths(app_name = u'gnumed', wx = wx) 894 895 candidates = [ 896 os.path.join(paths.system_app_data_dir, 'bitmaps', 'gm_icon-serpent_and_gnu.png'), 897 os.path.join(paths.local_base_dir, 'bitmaps', 'gm_icon-serpent_and_gnu.png'), 898 os.path.join(paths.system_app_data_dir, 'bitmaps', 'serpent.png'), 899 os.path.join(paths.local_base_dir, 'bitmaps', 'serpent.png') 900 ] 901 902 found_as = None 903 for candidate in candidates: 904 try: 905 open(candidate, 'r').close() 906 found_as = candidate 907 break 908 except IOError: 909 _log.debug('icon not found in [%s]', candidate) 910 911 if found_as is None: 912 _log.warning('no icon file found, falling back to builtin (ugly) icon') 913 icon_bmp_data = wx.BitmapFromXPMData(cPickle.loads(zlib.decompress(__icon_serpent))) 914 icon.CopyFromBitmap(icon_bmp_data) 915 else: 916 _log.debug('icon found in [%s]', found_as) 917 icon = wx.EmptyIcon() 918 try: 919 icon.LoadFile(found_as, wx.BITMAP_TYPE_ANY) #_PNG 920 except AttributeError: 921 _log.exception(u"this platform doesn't support wx.Icon().LoadFile()") 922 923 return icon
924 #=========================================================================== 925 # main 926 #--------------------------------------------------------------------------- 927 if __name__ == '__main__': 928 929 if len(sys.argv) < 2: 930 sys.exit() 931 932 if sys.argv[1] != 'test': 933 sys.exit() 934 935 #-----------------------------------------------------------------------
936 - def test_input2decimal():
937 938 tests = [ 939 [None, False], 940 941 ['', False], 942 [' 0 ', True, 0], 943 944 [0, True, 0], 945 [0.0, True, 0], 946 [.0, True, 0], 947 ['0', True, 0], 948 ['0.0', True, 0], 949 ['0,0', True, 0], 950 ['00.0', True, 0], 951 ['.0', True, 0], 952 [',0', True, 0], 953 954 [0.1, True, decimal.Decimal('0.1')], 955 [.01, True, decimal.Decimal('0.01')], 956 ['0.1', True, decimal.Decimal('0.1')], 957 ['0,1', True, decimal.Decimal('0.1')], 958 ['00.1', True, decimal.Decimal('0.1')], 959 ['.1', True, decimal.Decimal('0.1')], 960 [',1', True, decimal.Decimal('0.1')], 961 962 [1, True, 1], 963 [1.0, True, 1], 964 ['1', True, 1], 965 ['1.', True, 1], 966 ['1,', True, 1], 967 ['1.0', True, 1], 968 ['1,0', True, 1], 969 ['01.0', True, 1], 970 ['01,0', True, 1], 971 [' 01, ', True, 1], 972 ] 973 for test in tests: 974 conversion_worked, result = input2decimal(initial = test[0]) 975 976 expected2work = test[1] 977 978 if conversion_worked: 979 if expected2work: 980 if result == test[2]: 981 continue 982 else: 983 print "ERROR (conversion result wrong): >%s<, expected >%s<, got >%s<" % (test[0], test[2], result) 984 else: 985 print "ERROR (conversion worked but was expected to fail): >%s<, got >%s<" % (test[0], result) 986 else: 987 if not expected2work: 988 continue 989 else: 990 print "ERROR (conversion failed but was expected to work): >%s<, expected >%s<" % (test[0], test[2])
991 #-----------------------------------------------------------------------
992 - def test_coalesce():
993 print 'testing coalesce()' 994 print "------------------" 995 tests = [ 996 [None, 'something other than <None>', None, None, 'something other than <None>'], 997 ['Captain', 'Mr.', '%s.'[:4], 'Mr.', 'Capt.'], 998 ['value to test', 'test 3 failed', 'template with "%s" included', None, 'template with "value to test" included'], 999 ['value to test', 'test 4 failed', 'template with value not included', None, 'template with value not included'], 1000 [None, 'initial value was None', 'template_initial: %s', None, 'initial value was None'], 1001 [None, 'initial value was None', 'template_initial: %%(abc)s', None, 'initial value was None'] 1002 ] 1003 passed = True 1004 for test in tests: 1005 result = coalesce ( 1006 initial = test[0], 1007 instead = test[1], 1008 template_initial = test[2], 1009 template_instead = test[3] 1010 ) 1011 if result != test[4]: 1012 print "ERROR" 1013 print "coalesce: (%s, %s, %s, %s)" % (test[0], test[1], test[2], test[3]) 1014 print "expected:", test[4] 1015 print "received:", result 1016 passed = False 1017 1018 if passed: 1019 print "passed" 1020 else: 1021 print "failed" 1022 return passed
1023 #-----------------------------------------------------------------------
1024 - def test_capitalize():
1025 print 'testing capitalize() ...' 1026 success = True 1027 pairs = [ 1028 # [original, expected result, CAPS mode] 1029 [u'Boot', u'Boot', CAPS_FIRST_ONLY], 1030 [u'boot', u'Boot', CAPS_FIRST_ONLY], 1031 [u'booT', u'Boot', CAPS_FIRST_ONLY], 1032 [u'BoOt', u'Boot', CAPS_FIRST_ONLY], 1033 [u'boots-Schau', u'Boots-Schau', CAPS_WORDS], 1034 [u'boots-sChau', u'Boots-Schau', CAPS_WORDS], 1035 [u'boot camp', u'Boot Camp', CAPS_WORDS], 1036 [u'fahrner-Kampe', u'Fahrner-Kampe', CAPS_NAMES], 1037 [u'häkkönen', u'Häkkönen', CAPS_NAMES], 1038 [u'McBurney', u'McBurney', CAPS_NAMES], 1039 [u'mcBurney', u'McBurney', CAPS_NAMES], 1040 [u'blumberg', u'Blumberg', CAPS_NAMES], 1041 [u'roVsing', u'RoVsing', CAPS_NAMES], 1042 [u'Özdemir', u'Özdemir', CAPS_NAMES], 1043 [u'özdemir', u'Özdemir', CAPS_NAMES], 1044 ] 1045 for pair in pairs: 1046 result = capitalize(pair[0], pair[2]) 1047 if result != pair[1]: 1048 success = False 1049 print 'ERROR (caps mode %s): "%s" -> "%s", expected "%s"' % (pair[2], pair[0], result, pair[1]) 1050 1051 if success: 1052 print "... SUCCESS" 1053 1054 return success
1055 #-----------------------------------------------------------------------
1056 - def test_import_module():
1057 print "testing import_module_from_directory()" 1058 path = sys.argv[1] 1059 name = sys.argv[2] 1060 try: 1061 mod = import_module_from_directory(module_path = path, module_name = name) 1062 except: 1063 print "module import failed, see log" 1064 return False 1065 1066 print "module import succeeded", mod 1067 print dir(mod) 1068 return True
1069 #-----------------------------------------------------------------------
1070 - def test_mkdir():
1071 print "testing mkdir()" 1072 mkdir(sys.argv[1])
1073 #-----------------------------------------------------------------------
1074 - def test_send_mail():
1075 msg = u""" 1076 To: %s 1077 From: %s 1078 Subject: gmTools test suite mail 1079 1080 This is a test mail from the gmTools.py module. 1081 """ % (default_mail_receiver, default_mail_sender) 1082 print "mail sending succeeded:", send_mail ( 1083 receiver = [default_mail_receiver, u'karsten.hilbert@gmx.net'], 1084 message = msg, 1085 auth = {'user': default_mail_sender, 'password': u'gnumed-at-gmx-net'}, # u'gm/bugs/gmx' 1086 debug = True, 1087 attachments = [sys.argv[0]] 1088 )
1089 #-----------------------------------------------------------------------
1090 - def test_gmPaths():
1091 print "testing gmPaths()" 1092 print "-----------------" 1093 paths = gmPaths(wx=None, app_name='gnumed') 1094 print "user config dir:", paths.user_config_dir 1095 print "system config dir:", paths.system_config_dir 1096 print "local base dir:", paths.local_base_dir 1097 print "system app data dir:", paths.system_app_data_dir 1098 print "working directory :", paths.working_dir
1099 #-----------------------------------------------------------------------
1100 - def test_none_if():
1101 print "testing none_if()" 1102 print "-----------------" 1103 tests = [ 1104 [None, None, None], 1105 ['a', 'a', None], 1106 ['a', 'b', 'a'], 1107 ['a', None, 'a'], 1108 [None, 'a', None], 1109 [1, 1, None], 1110 [1, 2, 1], 1111 [1, None, 1], 1112 [None, 1, None] 1113 ] 1114 1115 for test in tests: 1116 if none_if(value = test[0], none_equivalent = test[1]) != test[2]: 1117 print 'ERROR: none_if(%s) returned [%s], expected [%s]' % (test[0], none_if(test[0], test[1]), test[2]) 1118 1119 return True
1120 #-----------------------------------------------------------------------
1121 - def test_bool2str():
1122 tests = [ 1123 [True, 'Yes', 'Yes', 'Yes'], 1124 [False, 'OK', 'not OK', 'not OK'] 1125 ] 1126 for test in tests: 1127 if bool2str(test[0], test[1], test[2]) != test[3]: 1128 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]) 1129 1130 return True
1131 #-----------------------------------------------------------------------
1132 - def test_bool2subst():
1133 1134 print bool2subst(True, 'True', 'False', 'is None') 1135 print bool2subst(False, 'True', 'False', 'is None') 1136 print bool2subst(None, 'True', 'False', 'is None')
1137 #-----------------------------------------------------------------------
1138 - def test_get_unique_filename():
1139 print get_unique_filename() 1140 print get_unique_filename(prefix='test-') 1141 print get_unique_filename(suffix='tst') 1142 print get_unique_filename(prefix='test-', suffix='tst') 1143 print get_unique_filename(tmp_dir='/home/ncq/Archiv/')
1144 #-----------------------------------------------------------------------
1145 - def test_size2str():
1146 print "testing size2str()" 1147 print "------------------" 1148 tests = [0, 1, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000, 1000000000000, 10000000000000] 1149 for test in tests: 1150 print size2str(test)
1151 #-----------------------------------------------------------------------
1152 - def test_unwrap():
1153 1154 test = """ 1155 second line\n 1156 3rd starts with tab \n 1157 4th with a space \n 1158 1159 6th 1160 1161 """ 1162 print unwrap(text = test, max_length = 25)
1163 #-----------------------------------------------------------------------
1164 - def test_wrap():
1165 test = 'line 1\nline 2\nline 3' 1166 1167 print "wrap 5-6-7 initial 0, subsequent 0" 1168 print wrap(test, 5) 1169 print 1170 print wrap(test, 6) 1171 print 1172 print wrap(test, 7) 1173 print "-------" 1174 raw_input() 1175 print "wrap 5 initial 1-1-3, subsequent 1-3-1" 1176 print wrap(test, 5, u' ', u' ') 1177 print 1178 print wrap(test, 5, u' ', u' ') 1179 print 1180 print wrap(test, 5, u' ', u' ') 1181 print "-------" 1182 raw_input() 1183 print "wrap 6 initial 1-1-3, subsequent 1-3-1" 1184 print wrap(test, 6, u' ', u' ') 1185 print 1186 print wrap(test, 6, u' ', u' ') 1187 print 1188 print wrap(test, 6, u' ', u' ') 1189 print "-------" 1190 raw_input() 1191 print "wrap 7 initial 1-1-3, subsequent 1-3-1" 1192 print wrap(test, 7, u' ', u' ') 1193 print 1194 print wrap(test, 7, u' ', u' ') 1195 print 1196 print wrap(test, 7, u' ', u' ')
1197 #-----------------------------------------------------------------------
1198 - def test_check_for_update():
1199 1200 test_data = [ 1201 ('http://www.gnumed.de/downloads/gnumed-versions.txt', None, None, False), 1202 ('file:///home/ncq/gm-versions.txt', None, None, False), 1203 ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.1', False), 1204 ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.1', True), 1205 ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.5', True) 1206 ] 1207 1208 for test in test_data: 1209 print "arguments:", test 1210 found, msg = check_for_update(test[0], test[1], test[2], test[3]) 1211 print msg 1212 1213 return
1214 #-----------------------------------------------------------------------
1215 - def test_md5():
1216 print '%s: %s' % (sys.argv[2], file2md5(sys.argv[2]))
1217 #----------------------------------------------------------------------- 1218 #test_check_for_update() 1219 #test_coalesce() 1220 #test_capitalize() 1221 #test_import_module() 1222 #test_mkdir() 1223 #test_send_mail() 1224 test_gmPaths() 1225 #test_none_if() 1226 #test_bool2str() 1227 #test_bool2subst() 1228 #test_get_unique_filename() 1229 #test_size2str() 1230 #test_wrap() 1231 #test_input2decimal() 1232 #test_unwrap() 1233 #test_md5() 1234 1235 #=========================================================================== 1236