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