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 default_csv_reader_rest_key = u'list_of_values_of_unknown_fields' 551
552 -def unicode_csv_reader(unicode_csv_data, dialect=csv.excel, encoding='utf-8', **kwargs):
553 554 # csv.py doesn't do Unicode; encode temporarily as UTF-8: 555 try: 556 is_dict_reader = kwargs['dict'] 557 del kwargs['dict'] 558 if is_dict_reader is not True: 559 raise KeyError 560 kwargs['restkey'] = default_csv_reader_rest_key 561 csv_reader = csv.DictReader(unicode2charset_encoder(unicode_csv_data), dialect=dialect, **kwargs) 562 except KeyError: 563 is_dict_reader = False 564 csv_reader = csv.reader(unicode2charset_encoder(unicode_csv_data), dialect=dialect, **kwargs) 565 566 for row in csv_reader: 567 # decode ENCODING back to Unicode, cell by cell: 568 if is_dict_reader: 569 for key in row.keys(): 570 if key == default_csv_reader_rest_key: 571 old_data = row[key] 572 new_data = [] 573 for val in old_data: 574 new_data.append(unicode(val, encoding)) 575 row[key] = new_data 576 if default_csv_reader_rest_key not in csv_reader.fieldnames: 577 csv_reader.fieldnames.append(default_csv_reader_rest_key) 578 else: 579 row[key] = unicode(row[key], encoding) 580 yield row 581 else: 582 yield [ unicode(cell, encoding) for cell in row ]
583 #yield [unicode(cell, 'utf-8') for cell in row] 584 #---------------------------------------------------------------------------
585 -def get_unique_filename(prefix=None, suffix=None, tmp_dir=None):
586 """This introduces a race condition between the file.close() and 587 actually using the filename. 588 589 The file will not exist after calling this function. 590 """ 591 if tmp_dir is not None: 592 if ( 593 not os.access(tmp_dir, os.F_OK) 594 or 595 not os.access(tmp_dir, os.X_OK | os.W_OK) 596 ): 597 _log.info('cannot find temporary dir [%s], using system default', tmp_dir) 598 tmp_dir = None 599 600 kwargs = {'dir': tmp_dir} 601 602 if prefix is None: 603 kwargs['prefix'] = 'gnumed-' 604 else: 605 kwargs['prefix'] = prefix 606 607 if suffix in [None, u'']: 608 kwargs['suffix'] = '.tmp' 609 else: 610 if not suffix.startswith('.'): 611 suffix = '.' + suffix 612 kwargs['suffix'] = suffix 613 614 f = tempfile.NamedTemporaryFile(**kwargs) 615 filename = f.name 616 f.close() 617 618 return filename
619 #===========================================================================
620 -def import_module_from_directory(module_path=None, module_name=None, always_remove_path=False):
621 """Import a module from any location.""" 622 623 remove_path = always_remove_path or False 624 if module_path not in sys.path: 625 _log.info('appending to sys.path: [%s]' % module_path) 626 sys.path.append(module_path) 627 remove_path = True 628 629 _log.debug('will remove import path: %s', remove_path) 630 631 if module_name.endswith('.py'): 632 module_name = module_name[:-3] 633 634 try: 635 module = __import__(module_name) 636 except StandardError: 637 _log.exception('cannot __import__() module [%s] from [%s]' % (module_name, module_path)) 638 while module_path in sys.path: 639 sys.path.remove(module_path) 640 raise 641 642 _log.info('imported module [%s] as [%s]' % (module_name, module)) 643 if remove_path: 644 while module_path in sys.path: 645 sys.path.remove(module_path) 646 647 return module
648 #=========================================================================== 649 # text related tools 650 #--------------------------------------------------------------------------- 651 _kB = 1024 652 _MB = 1024 * _kB 653 _GB = 1024 * _MB 654 _TB = 1024 * _GB 655 _PB = 1024 * _TB 656 #---------------------------------------------------------------------------
657 -def size2str(size=0, template='%s'):
658 if size == 1: 659 return template % _('1 Byte') 660 if size < 10 * _kB: 661 return template % _('%s Bytes') % size 662 if size < _MB: 663 return template % u'%.1f kB' % (float(size) / _kB) 664 if size < _GB: 665 return template % u'%.1f MB' % (float(size) / _MB) 666 if size < _TB: 667 return template % u'%.1f GB' % (float(size) / _GB) 668 if size < _PB: 669 return template % u'%.1f TB' % (float(size) / _TB) 670 return template % u'%.1f PB' % (float(size) / _PB)
671 #---------------------------------------------------------------------------
672 -def bool2subst(boolean=None, true_return=True, false_return=False, none_return=None):
673 if boolean is None: 674 return none_return 675 if boolean is True: 676 return true_return 677 if boolean is False: 678 return false_return 679 raise ValueError('bool2subst(): <boolean> arg must be either of True, False, None')
680 #---------------------------------------------------------------------------
681 -def bool2str(boolean=None, true_str='True', false_str='False'):
682 return bool2subst ( 683 boolean = bool(boolean), 684 true_return = true_str, 685 false_return = false_str 686 )
687 #---------------------------------------------------------------------------
688 -def none_if(value=None, none_equivalent=None):
689 """Modelled after the SQL NULLIF function.""" 690 if value == none_equivalent: 691 return None 692 return value
693 #---------------------------------------------------------------------------
694 -def coalesce(initial=None, instead=None, template_initial=None, template_instead=None, none_equivalents=None, function_initial=None):
695 """Modelled after the SQL coalesce function. 696 697 To be used to simplify constructs like: 698 699 if initial is None (or in none_equivalents): 700 real_value = (template_instead % instead) or instead 701 else: 702 real_value = (template_initial % initial) or initial 703 print real_value 704 705 @param initial: the value to be tested for <None> 706 @type initial: any Python type, must have a __str__ method if template_initial is not None 707 @param instead: the value to be returned if <initial> is None 708 @type instead: any Python type, must have a __str__ method if template_instead is not None 709 @param template_initial: if <initial> is returned replace the value into this template, must contain one <%s> 710 @type template_initial: string or None 711 @param template_instead: if <instead> is returned replace the value into this template, must contain one <%s> 712 @type template_instead: string or None 713 714 Ideas: 715 - list of insteads: initial, [instead, template], [instead, template], [instead, template], template_initial, ... 716 """ 717 if none_equivalents is None: 718 none_equivalents = [None] 719 720 if initial in none_equivalents: 721 722 if template_instead is None: 723 return instead 724 725 return template_instead % instead 726 727 if function_initial is not None: 728 funcname, args = function_initial 729 func = getattr(initial, funcname) 730 initial = func(args) 731 732 if template_initial is None: 733 return initial 734 735 try: 736 return template_initial % initial 737 except TypeError: 738 return template_initial
739 #---------------------------------------------------------------------------
740 -def __cap_name(match_obj=None):
741 val = match_obj.group(0).lower() 742 if val in ['von', 'van', 'de', 'la', 'l', 'der', 'den']: # FIXME: this needs to expand, configurable ? 743 return val 744 buf = list(val) 745 buf[0] = buf[0].upper() 746 for part in ['mac', 'mc', 'de', 'la']: 747 if len(val) > len(part) and val[:len(part)] == part: 748 buf[len(part)] = buf[len(part)].upper() 749 return ''.join(buf)
750 #---------------------------------------------------------------------------
751 -def capitalize(text=None, mode=CAPS_NAMES):
752 """Capitalize the first character but leave the rest alone. 753 754 Note that we must be careful about the locale, this may 755 have issues ! However, for UTF strings it should just work. 756 """ 757 if (mode is None) or (mode == CAPS_NONE): 758 return text 759 760 if mode == CAPS_FIRST: 761 if len(text) == 1: 762 return text[0].upper() 763 return text[0].upper() + text[1:] 764 765 if mode == CAPS_ALLCAPS: 766 return text.upper() 767 768 if mode == CAPS_FIRST_ONLY: 769 if len(text) == 1: 770 return text[0].upper() 771 return text[0].upper() + text[1:].lower() 772 773 if mode == CAPS_WORDS: 774 return regex.sub(ur'(\w)(\w+)', lambda x: x.group(1).upper() + x.group(2).lower(), text) 775 776 if mode == CAPS_NAMES: 777 #return regex.sub(r'\w+', __cap_name, text) 778 return capitalize(text=text, mode=CAPS_FIRST) # until fixed 779 780 print "ERROR: invalid capitalization mode: [%s], leaving input as is" % mode 781 return text
782 #---------------------------------------------------------------------------
783 -def input2decimal(initial=None):
784 785 val = initial 786 787 # float ? -> to string first 788 if type(val) == type(1.4): 789 val = str(val) 790 791 # string ? -> "," to "." 792 if isinstance(val, basestring): 793 val = val.replace(',', '.', 1) 794 val = val.strip() 795 # val = val.lstrip('0') 796 # if val.startswith('.'): 797 # val = '0' + val 798 799 try: 800 d = decimal.Decimal(val) 801 return True, d 802 except (TypeError, decimal.InvalidOperation): 803 return False, val
804 #---------------------------------------------------------------------------
805 -def wrap(text=None, width=None, initial_indent=u'', subsequent_indent=u'', eol=u'\n'):
806 """A word-wrap function that preserves existing line breaks 807 and most spaces in the text. Expects that existing line 808 breaks are posix newlines (\n). 809 """ 810 wrapped = initial_indent + reduce ( 811 lambda line, word, width=width: '%s%s%s' % ( 812 line, 813 ' \n'[(len(line) - line.rfind('\n') - 1 + len(word.split('\n',1)[0]) >= width)], 814 word 815 ), 816 text.split(' ') 817 ) 818 819 if subsequent_indent != u'': 820 wrapped = (u'\n%s' % subsequent_indent).join(wrapped.split('\n')) 821 822 if eol != u'\n': 823 wrapped = wrapped.replace('\n', eol) 824 825 return wrapped
826 #---------------------------------------------------------------------------
827 -def unwrap(text=None, max_length=None, strip_whitespace=True, remove_empty_lines=True, line_separator = u' // '):
828 829 text = text.replace(u'\r', u'') 830 lines = text.split(u'\n') 831 text = u'' 832 for line in lines: 833 834 if strip_whitespace: 835 line = line.strip().strip(u'\t').strip() 836 837 if remove_empty_lines: 838 if line == u'': 839 continue 840 841 text += (u'%s%s' % (line, line_separator)) 842 843 text = text.rstrip(line_separator) 844 845 if max_length is not None: 846 text = text[:max_length] 847 848 text = text.rstrip(line_separator) 849 850 return text
851 #---------------------------------------------------------------------------
852 -def tex_escape_string(text=None):
853 """check for special latex-characters and transform them""" 854 855 text = text.replace(u'\\', u'$\\backslash$') 856 text = text.replace(u'{', u'\\{') 857 text = text.replace(u'}', u'\\}') 858 text = text.replace(u'%', u'\\%') 859 text = text.replace(u'&', u'\\&') 860 text = text.replace(u'#', u'\\#') 861 text = text.replace(u'$', u'\\$') 862 text = text.replace(u'_', u'\\_') 863 864 text = text.replace(u'^', u'\\verb#^#') 865 text = text.replace('~','\\verb#~#') 866 867 return text
868 #---------------------------------------------------------------------------
869 -def prompted_input(prompt=None, default=None):
870 """Obtains entry from standard input. 871 872 prompt: Prompt text to display in standard output 873 default: Default value (for user to press enter only) 874 CTRL-C: aborts and returns None 875 """ 876 if prompt is None: 877 msg = u'(CTRL-C aborts)' 878 else: 879 msg = u'%s (CTRL-C aborts)' % prompt 880 881 if default is None: 882 msg = msg + u': ' 883 else: 884 msg = u'%s [%s]: ' % (msg, default) 885 886 try: 887 usr_input = raw_input(msg) 888 except KeyboardInterrupt: 889 return None 890 891 if usr_input == '': 892 return default 893 894 return usr_input
895 896 #=========================================================================== 897 # image handling tools 898 #--------------------------------------------------------------------------- 899 # builtin (ugly but tried and true) fallback icon 900 __icon_serpent = \ 901 """x\xdae\x8f\xb1\x0e\x83 \x10\x86w\x9f\xe2\x92\x1blb\xf2\x07\x96\xeaH:0\xd6\ 902 \xc1\x85\xd5\x98N5\xa5\xef?\xf5N\xd0\x8a\xdcA\xc2\xf7qw\x84\xdb\xfa\xb5\xcd\ 903 \xd4\xda;\xc9\x1a\xc8\xb6\xcd<\xb5\xa0\x85\x1e\xeb\xbc\xbc7b!\xf6\xdeHl\x1c\ 904 \x94\x073\xec<*\xf7\xbe\xf7\x99\x9d\xb21~\xe7.\xf5\x1f\x1c\xd3\xbdVlL\xc2\ 905 \xcf\xf8ye\xd0\x00\x90\x0etH \x84\x80B\xaa\x8a\x88\x85\xc4(U\x9d$\xfeR;\xc5J\ 906 \xa6\x01\xbbt9\xceR\xc8\x81e_$\x98\xb9\x9c\xa9\x8d,y\xa9t\xc8\xcf\x152\xe0x\ 907 \xe9$\xf5\x07\x95\x0cD\x95t:\xb1\x92\xae\x9cI\xa8~\x84\x1f\xe0\xa3ec""" 908
909 -def get_icon(wx=None):
910 911 paths = gmPaths(app_name = u'gnumed', wx = wx) 912 913 candidates = [ 914 os.path.join(paths.system_app_data_dir, 'bitmaps', 'gm_icon-serpent_and_gnu.png'), 915 os.path.join(paths.local_base_dir, 'bitmaps', 'gm_icon-serpent_and_gnu.png'), 916 os.path.join(paths.system_app_data_dir, 'bitmaps', 'serpent.png'), 917 os.path.join(paths.local_base_dir, 'bitmaps', 'serpent.png') 918 ] 919 920 found_as = None 921 for candidate in candidates: 922 try: 923 open(candidate, 'r').close() 924 found_as = candidate 925 break 926 except IOError: 927 _log.debug('icon not found in [%s]', candidate) 928 929 if found_as is None: 930 _log.warning('no icon file found, falling back to builtin (ugly) icon') 931 icon_bmp_data = wx.BitmapFromXPMData(cPickle.loads(zlib.decompress(__icon_serpent))) 932 icon.CopyFromBitmap(icon_bmp_data) 933 else: 934 _log.debug('icon found in [%s]', found_as) 935 icon = wx.EmptyIcon() 936 try: 937 icon.LoadFile(found_as, wx.BITMAP_TYPE_ANY) #_PNG 938 except AttributeError: 939 _log.exception(u"this platform doesn't support wx.Icon().LoadFile()") 940 941 return icon
942 #=========================================================================== 943 # main 944 #--------------------------------------------------------------------------- 945 if __name__ == '__main__': 946 947 if len(sys.argv) < 2: 948 sys.exit() 949 950 if sys.argv[1] != 'test': 951 sys.exit() 952 953 #-----------------------------------------------------------------------
954 - def test_input2decimal():
955 956 tests = [ 957 [None, False], 958 959 ['', False], 960 [' 0 ', True, 0], 961 962 [0, True, 0], 963 [0.0, True, 0], 964 [.0, True, 0], 965 ['0', True, 0], 966 ['0.0', True, 0], 967 ['0,0', True, 0], 968 ['00.0', True, 0], 969 ['.0', True, 0], 970 [',0', True, 0], 971 972 [0.1, True, decimal.Decimal('0.1')], 973 [.01, True, decimal.Decimal('0.01')], 974 ['0.1', True, decimal.Decimal('0.1')], 975 ['0,1', True, decimal.Decimal('0.1')], 976 ['00.1', True, decimal.Decimal('0.1')], 977 ['.1', True, decimal.Decimal('0.1')], 978 [',1', True, decimal.Decimal('0.1')], 979 980 [1, True, 1], 981 [1.0, True, 1], 982 ['1', True, 1], 983 ['1.', True, 1], 984 ['1,', True, 1], 985 ['1.0', True, 1], 986 ['1,0', True, 1], 987 ['01.0', True, 1], 988 ['01,0', True, 1], 989 [' 01, ', True, 1], 990 ] 991 for test in tests: 992 conversion_worked, result = input2decimal(initial = test[0]) 993 994 expected2work = test[1] 995 996 if conversion_worked: 997 if expected2work: 998 if result == test[2]: 999 continue 1000 else: 1001 print "ERROR (conversion result wrong): >%s<, expected >%s<, got >%s<" % (test[0], test[2], result) 1002 else: 1003 print "ERROR (conversion worked but was expected to fail): >%s<, got >%s<" % (test[0], result) 1004 else: 1005 if not expected2work: 1006 continue 1007 else: 1008 print "ERROR (conversion failed but was expected to work): >%s<, expected >%s<" % (test[0], test[2])
1009 #-----------------------------------------------------------------------
1010 - def test_coalesce():
1011 1012 import datetime as dt 1013 print coalesce(initial = dt.datetime.now(), template_initial = u'-- %s --', function_initial = ('strftime', u'%Y-%m-%d')) 1014 1015 print 'testing coalesce()' 1016 print "------------------" 1017 tests = [ 1018 [None, 'something other than <None>', None, None, 'something other than <None>'], 1019 ['Captain', 'Mr.', '%s.'[:4], 'Mr.', 'Capt.'], 1020 ['value to test', 'test 3 failed', 'template with "%s" included', None, 'template with "value to test" included'], 1021 ['value to test', 'test 4 failed', 'template with value not included', None, 'template with value not included'], 1022 [None, 'initial value was None', 'template_initial: %s', None, 'initial value was None'], 1023 [None, 'initial value was None', 'template_initial: %%(abc)s', None, 'initial value was None'] 1024 ] 1025 passed = True 1026 for test in tests: 1027 result = coalesce ( 1028 initial = test[0], 1029 instead = test[1], 1030 template_initial = test[2], 1031 template_instead = test[3] 1032 ) 1033 if result != test[4]: 1034 print "ERROR" 1035 print "coalesce: (%s, %s, %s, %s)" % (test[0], test[1], test[2], test[3]) 1036 print "expected:", test[4] 1037 print "received:", result 1038 passed = False 1039 1040 if passed: 1041 print "passed" 1042 else: 1043 print "failed" 1044 return passed
1045 #-----------------------------------------------------------------------
1046 - def test_capitalize():
1047 print 'testing capitalize() ...' 1048 success = True 1049 pairs = [ 1050 # [original, expected result, CAPS mode] 1051 [u'Boot', u'Boot', CAPS_FIRST_ONLY], 1052 [u'boot', u'Boot', CAPS_FIRST_ONLY], 1053 [u'booT', u'Boot', CAPS_FIRST_ONLY], 1054 [u'BoOt', u'Boot', CAPS_FIRST_ONLY], 1055 [u'boots-Schau', u'Boots-Schau', CAPS_WORDS], 1056 [u'boots-sChau', u'Boots-Schau', CAPS_WORDS], 1057 [u'boot camp', u'Boot Camp', CAPS_WORDS], 1058 [u'fahrner-Kampe', u'Fahrner-Kampe', CAPS_NAMES], 1059 [u'häkkönen', u'Häkkönen', CAPS_NAMES], 1060 [u'McBurney', u'McBurney', CAPS_NAMES], 1061 [u'mcBurney', u'McBurney', CAPS_NAMES], 1062 [u'blumberg', u'Blumberg', CAPS_NAMES], 1063 [u'roVsing', u'RoVsing', CAPS_NAMES], 1064 [u'Özdemir', u'Özdemir', CAPS_NAMES], 1065 [u'özdemir', u'Özdemir', CAPS_NAMES], 1066 ] 1067 for pair in pairs: 1068 result = capitalize(pair[0], pair[2]) 1069 if result != pair[1]: 1070 success = False 1071 print 'ERROR (caps mode %s): "%s" -> "%s", expected "%s"' % (pair[2], pair[0], result, pair[1]) 1072 1073 if success: 1074 print "... SUCCESS" 1075 1076 return success
1077 #-----------------------------------------------------------------------
1078 - def test_import_module():
1079 print "testing import_module_from_directory()" 1080 path = sys.argv[1] 1081 name = sys.argv[2] 1082 try: 1083 mod = import_module_from_directory(module_path = path, module_name = name) 1084 except: 1085 print "module import failed, see log" 1086 return False 1087 1088 print "module import succeeded", mod 1089 print dir(mod) 1090 return True
1091 #-----------------------------------------------------------------------
1092 - def test_mkdir():
1093 print "testing mkdir()" 1094 mkdir(sys.argv[1])
1095 #-----------------------------------------------------------------------
1096 - def test_send_mail():
1097 msg = u""" 1098 To: %s 1099 From: %s 1100 Subject: gmTools test suite mail 1101 1102 This is a test mail from the gmTools.py module. 1103 """ % (default_mail_receiver, default_mail_sender) 1104 print "mail sending succeeded:", send_mail ( 1105 receiver = [default_mail_receiver, u'karsten.hilbert@gmx.net'], 1106 message = msg, 1107 auth = {'user': default_mail_sender, 'password': u'gnumed-at-gmx-net'}, # u'gm/bugs/gmx' 1108 debug = True, 1109 attachments = [sys.argv[0]] 1110 )
1111 #-----------------------------------------------------------------------
1112 - def test_gmPaths():
1113 print "testing gmPaths()" 1114 print "-----------------" 1115 paths = gmPaths(wx=None, app_name='gnumed') 1116 print "user config dir:", paths.user_config_dir 1117 print "system config dir:", paths.system_config_dir 1118 print "local base dir:", paths.local_base_dir 1119 print "system app data dir:", paths.system_app_data_dir 1120 print "working directory :", paths.working_dir
1121 #-----------------------------------------------------------------------
1122 - def test_none_if():
1123 print "testing none_if()" 1124 print "-----------------" 1125 tests = [ 1126 [None, None, None], 1127 ['a', 'a', None], 1128 ['a', 'b', 'a'], 1129 ['a', None, 'a'], 1130 [None, 'a', None], 1131 [1, 1, None], 1132 [1, 2, 1], 1133 [1, None, 1], 1134 [None, 1, None] 1135 ] 1136 1137 for test in tests: 1138 if none_if(value = test[0], none_equivalent = test[1]) != test[2]: 1139 print 'ERROR: none_if(%s) returned [%s], expected [%s]' % (test[0], none_if(test[0], test[1]), test[2]) 1140 1141 return True
1142 #-----------------------------------------------------------------------
1143 - def test_bool2str():
1144 tests = [ 1145 [True, 'Yes', 'Yes', 'Yes'], 1146 [False, 'OK', 'not OK', 'not OK'] 1147 ] 1148 for test in tests: 1149 if bool2str(test[0], test[1], test[2]) != test[3]: 1150 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]) 1151 1152 return True
1153 #-----------------------------------------------------------------------
1154 - def test_bool2subst():
1155 1156 print bool2subst(True, 'True', 'False', 'is None') 1157 print bool2subst(False, 'True', 'False', 'is None') 1158 print bool2subst(None, 'True', 'False', 'is None')
1159 #-----------------------------------------------------------------------
1160 - def test_get_unique_filename():
1161 print get_unique_filename() 1162 print get_unique_filename(prefix='test-') 1163 print get_unique_filename(suffix='tst') 1164 print get_unique_filename(prefix='test-', suffix='tst') 1165 print get_unique_filename(tmp_dir='/home/ncq/Archiv/')
1166 #-----------------------------------------------------------------------
1167 - def test_size2str():
1168 print "testing size2str()" 1169 print "------------------" 1170 tests = [0, 1, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000, 1000000000000, 10000000000000] 1171 for test in tests: 1172 print size2str(test)
1173 #-----------------------------------------------------------------------
1174 - def test_unwrap():
1175 1176 test = """ 1177 second line\n 1178 3rd starts with tab \n 1179 4th with a space \n 1180 1181 6th 1182 1183 """ 1184 print unwrap(text = test, max_length = 25)
1185 #-----------------------------------------------------------------------
1186 - def test_wrap():
1187 test = 'line 1\nline 2\nline 3' 1188 1189 print "wrap 5-6-7 initial 0, subsequent 0" 1190 print wrap(test, 5) 1191 print 1192 print wrap(test, 6) 1193 print 1194 print wrap(test, 7) 1195 print "-------" 1196 raw_input() 1197 print "wrap 5 initial 1-1-3, subsequent 1-3-1" 1198 print wrap(test, 5, u' ', u' ') 1199 print 1200 print wrap(test, 5, u' ', u' ') 1201 print 1202 print wrap(test, 5, u' ', u' ') 1203 print "-------" 1204 raw_input() 1205 print "wrap 6 initial 1-1-3, subsequent 1-3-1" 1206 print wrap(test, 6, u' ', u' ') 1207 print 1208 print wrap(test, 6, u' ', u' ') 1209 print 1210 print wrap(test, 6, u' ', u' ') 1211 print "-------" 1212 raw_input() 1213 print "wrap 7 initial 1-1-3, subsequent 1-3-1" 1214 print wrap(test, 7, u' ', u' ') 1215 print 1216 print wrap(test, 7, u' ', u' ') 1217 print 1218 print wrap(test, 7, u' ', u' ')
1219 #-----------------------------------------------------------------------
1220 - def test_check_for_update():
1221 1222 test_data = [ 1223 ('http://www.gnumed.de/downloads/gnumed-versions.txt', None, None, False), 1224 ('file:///home/ncq/gm-versions.txt', None, None, False), 1225 ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.1', False), 1226 ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.1', True), 1227 ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.5', True) 1228 ] 1229 1230 for test in test_data: 1231 print "arguments:", test 1232 found, msg = check_for_update(test[0], test[1], test[2], test[3]) 1233 print msg 1234 1235 return
1236 #-----------------------------------------------------------------------
1237 - def test_md5():
1238 print '%s: %s' % (sys.argv[2], file2md5(sys.argv[2]))
1239 #----------------------------------------------------------------------- 1240 #test_check_for_update() 1241 #test_coalesce() 1242 #test_capitalize() 1243 #test_import_module() 1244 #test_mkdir() 1245 #test_send_mail() 1246 test_gmPaths() 1247 #test_none_if() 1248 #test_bool2str() 1249 #test_bool2subst() 1250 #test_get_unique_filename() 1251 #test_size2str() 1252 #test_wrap() 1253 #test_input2decimal() 1254 #test_unwrap() 1255 #test_md5() 1256 1257 #=========================================================================== 1258