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  # $Id: gmTools.py,v 1.98 2010/01/17 19:47:10 ncq Exp $ 
   6  # $Source: /cvsroot/gnumed/gnumed/gnumed/client/pycommon/gmTools.py,v $ 
   7  __version__ = "$Revision: 1.98 $" 
   8  __author__ = "K. Hilbert <Karsten.Hilbert@gmx.net>" 
   9  __license__ = "GPL (details at http://www.gnu.org)" 
  10   
  11  # std libs 
  12  import re as regex, sys, os, os.path, csv, tempfile, logging 
  13  import urllib2 as wget, decimal, StringIO, MimeWriter, mimetypes, mimetools 
  14   
  15   
  16  # GNUmed libs 
  17  if __name__ == '__main__': 
  18          # for testing: 
  19          logging.basicConfig(level = logging.DEBUG) 
  20          sys.path.insert(0, '../../') 
  21          from Gnumed.pycommon import gmI18N 
  22          gmI18N.activate_locale() 
  23          gmI18N.install_domain() 
  24   
  25  from Gnumed.pycommon import gmBorg 
  26   
  27   
  28  _log = logging.getLogger('gm.tools') 
  29  _log.info(__version__) 
  30   
  31  # CAPitalization modes: 
  32  (       CAPS_NONE,                                      # don't touch it 
  33          CAPS_FIRST,                                     # CAP first char, leave rest as is 
  34          CAPS_ALLCAPS,                           # CAP all chars 
  35          CAPS_WORDS,                                     # CAP first char of every word 
  36          CAPS_NAMES,                                     # CAP in a way suitable for names (tries to be smart) 
  37          CAPS_FIRST_ONLY                         # CAP first char, lowercase the rest 
  38  ) = range(6) 
  39   
  40  default_mail_sender = u'gnumed@gmx.net' 
  41  default_mail_receiver = u'gnumed-devel@gnu.org' 
  42  default_mail_server = u'mail.gmx.net' 
  43   
  44   
  45  u_right_double_angle_quote = u'\u00AB'          # << 
  46  u_registered_trademark = u'\u00AE' 
  47  u_plus_minus = u'\u00B1' 
  48  u_left_double_angle_quote = u'\u00BB'           # >> 
  49  u_one_quarter = u'\u00BC' 
  50  u_one_half = u'\u00BD' 
  51  u_three_quarters = u'\u00BE' 
  52  u_ellipsis = u'\u2026' 
  53  u_left_arrow = u'\u2190' 
  54  u_right_arrow = u'\u2192' 
  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   
  70  #=========================================================================== 
71 -def check_for_update(url=None, current_branch=None, current_version=None, consider_latest_branch=False):
72 """Check for new releases at <url>. 73 74 Returns (bool, text). 75 True: new release available 76 False: up to date 77 None: don't know 78 """ 79 try: 80 remote_file = wget.urlopen(url) 81 except (wget.URLError, ValueError, OSError): 82 _log.exception("cannot retrieve version file from [%s]", url) 83 return (None, _('Cannot retrieve version information from:\n\n%s') % url) 84 85 _log.debug('retrieving version information from [%s]', url) 86 87 from Gnumed.pycommon import gmCfg2 88 cfg = gmCfg2.gmCfgData() 89 try: 90 cfg.add_stream_source(source = 'gm-versions', stream = remote_file) 91 except (UnicodeDecodeError): 92 remote_file.close() 93 _log.exception("cannot read version file from [%s]", url) 94 return (None, _('Cannot read version information from:\n\n%s') % url) 95 96 remote_file.close() 97 98 latest_branch = cfg.get('latest branch', 'branch', source_order = [('gm-versions', 'return')]) 99 latest_release_on_latest_branch = cfg.get('branch %s' % latest_branch, 'latest release', source_order = [('gm-versions', 'return')]) 100 latest_release_on_current_branch = cfg.get('branch %s' % current_branch, 'latest release', source_order = [('gm-versions', 'return')]) 101 102 cfg.remove_source('gm-versions') 103 104 _log.info('current release: %s', current_version) 105 _log.info('current branch: %s', current_branch) 106 _log.info('latest release on current branch: %s', latest_release_on_current_branch) 107 _log.info('latest branch: %s', latest_branch) 108 _log.info('latest release on latest branch: %s', latest_release_on_latest_branch) 109 110 # anything known ? 111 no_release_information_available = ( 112 ( 113 (latest_release_on_current_branch is None) and 114 (latest_release_on_latest_branch is None) 115 ) or ( 116 not consider_latest_branch and 117 (latest_release_on_current_branch is None) 118 ) 119 ) 120 if no_release_information_available: 121 _log.warning('no release information available') 122 msg = _('There is no version information available from:\n\n%s') % url 123 return (None, msg) 124 125 # up to date ? 126 if consider_latest_branch: 127 _log.debug('latest branch taken into account') 128 if current_version >= latest_release_on_latest_branch: 129 _log.debug('up to date: current version >= latest version on latest branch') 130 return (False, None) 131 if latest_release_on_latest_branch is None: 132 if current_version >= latest_release_on_current_branch: 133 _log.debug('up to date: current version >= latest version on current branch and no latest branch available') 134 return (False, None) 135 else: 136 _log.debug('latest branch not taken into account') 137 if current_version >= latest_release_on_current_branch: 138 _log.debug('up to date: current version >= latest version on current branch') 139 return (False, None) 140 141 new_release_on_current_branch_available = ( 142 (latest_release_on_current_branch is not None) and 143 (latest_release_on_current_branch > current_version) 144 ) 145 _log.info('%snew release on current branch available', bool2str(new_release_on_current_branch_available, '', 'no ')) 146 147 new_release_on_latest_branch_available = ( 148 (latest_branch is not None) 149 and 150 ( 151 (latest_branch > current_branch) or ( 152 (latest_branch == current_branch) and 153 (latest_release_on_latest_branch > current_version) 154 ) 155 ) 156 ) 157 _log.info('%snew release on latest branch available', bool2str(new_release_on_latest_branch_available, '', 'no ')) 158 159 if not (new_release_on_current_branch_available or new_release_on_latest_branch_available): 160 _log.debug('up to date: no new releases available') 161 return (False, None) 162 163 # not up to date 164 msg = _('A new version of GNUmed is available.\n\n') 165 msg += _(' Your current version: "%s"\n') % current_version 166 if consider_latest_branch: 167 if new_release_on_current_branch_available: 168 msg += u'\n' 169 msg += _(' New version: "%s"') % latest_release_on_current_branch 170 msg += u'\n' 171 msg += _(' - bug fixes only\n') 172 msg += _(' - database fixups may be needed\n') 173 if new_release_on_latest_branch_available: 174 if current_branch != latest_branch: 175 msg += u'\n' 176 msg += _(' New version: "%s"') % latest_release_on_latest_branch 177 msg += u'\n' 178 msg += _(' - bug fixes and new features\n') 179 msg += _(' - database upgrade required\n') 180 else: 181 msg += u'\n' 182 msg += _(' New version: "%s"') % latest_release_on_current_branch 183 msg += u'\n' 184 msg += _(' - bug fixes only\n') 185 msg += _(' - database fixups may be needed\n') 186 187 msg += u'\n\n' 188 msg += _( 189 'Note, however, that this version may not yet\n' 190 'be available *pre-packaged* for your system.' 191 ) 192 193 msg += u'\n\n' 194 msg += _('Details are found on <http://wiki.gnumed.de>.\n') 195 msg += u'\n' 196 msg += _('Version information loaded from:\n\n %s') % url 197 198 return (True, msg)
199 #===========================================================================
200 -def unicode2charset_encoder(unicode_csv_data, encoding='utf-8'):
201 for line in unicode_csv_data: 202 yield line.encode(encoding)
203 204 #def utf_8_encoder(unicode_csv_data): 205 # for line in unicode_csv_data: 206 # yield line.encode('utf-8') 207
208 -def unicode_csv_reader(unicode_csv_data, dialect=csv.excel, encoding='utf-8', **kwargs):
209 # csv.py doesn't do Unicode; encode temporarily as UTF-8: 210 try: 211 is_dict_reader = kwargs['dict'] 212 del kwargs['dict'] 213 if is_dict_reader is not True: 214 raise KeyError 215 csv_reader = csv.DictReader(unicode2charset_encoder(unicode_csv_data), dialect=dialect, **kwargs) 216 except KeyError: 217 is_dict_reader = False 218 csv_reader = csv.reader(unicode2charset_encoder(unicode_csv_data), dialect=dialect, **kwargs) 219 220 for row in csv_reader: 221 # decode ENCODING back to Unicode, cell by cell: 222 if is_dict_reader: 223 for key in row.keys(): 224 row[key] = unicode(row[key], encoding) 225 yield row 226 else: 227 yield [ unicode(cell, encoding) for cell in row ]
228 #yield [unicode(cell, 'utf-8') for cell in row] 229 #===========================================================================
230 -def handle_uncaught_exception_console(t, v, tb):
231 232 print ",========================================================" 233 print "| Unhandled exception caught !" 234 print "| Type :", t 235 print "| Value:", v 236 print "`========================================================" 237 _log.critical('unhandled exception caught', exc_info = (t,v,tb)) 238 sys.__excepthook__(t,v,tb)
239 #===========================================================================
240 -class gmPaths(gmBorg.cBorg):
241
242 - def __init__(self, app_name=None, wx=None):
243 """Setup pathes. 244 245 <app_name> will default to (name of the script - .py) 246 """ 247 try: 248 self.already_inited 249 return 250 except AttributeError: 251 pass 252 253 self.init_paths(app_name=app_name, wx=wx) 254 self.already_inited = True
255 #-------------------------------------- 256 # public API 257 #--------------------------------------
258 - def init_paths(self, app_name=None, wx=None):
259 260 if wx is None: 261 _log.debug('wxPython not available') 262 _log.debug('detecting paths directly') 263 264 if app_name is None: 265 app_name, ext = os.path.splitext(os.path.basename(sys.argv[0])) 266 _log.info('app name detected as [%s]', app_name) 267 else: 268 _log.info('app name passed in as [%s]', app_name) 269 270 # the user home, doesn't work in Wine so work around that 271 self.__home_dir = None 272 273 # where the main script (the "binary") is installed 274 self.local_base_dir = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '..', '.')) 275 276 # the current working dir at the OS 277 self.working_dir = os.path.abspath(os.curdir) 278 279 # user-specific config dir, usually below the home dir 280 #mkdir(os.path.expanduser(os.path.join('~', '.%s' % app_name))) 281 #self.user_config_dir = os.path.expanduser(os.path.join('~', '.%s' % app_name)) 282 mkdir(os.path.join(self.home_dir, '.%s' % app_name)) 283 self.user_config_dir = os.path.join(self.home_dir, '.%s' % app_name) 284 285 # system-wide config dir, usually below /etc/ under UN*X 286 try: 287 self.system_config_dir = os.path.join('/etc', app_name) 288 except ValueError: 289 #self.system_config_dir = self.local_base_dir 290 self.system_config_dir = self.user_config_dir 291 292 # system-wide application data dir 293 try: 294 self.system_app_data_dir = os.path.join(sys.prefix, 'share', app_name) 295 except ValueError: 296 self.system_app_data_dir = self.local_base_dir 297 298 self.__log_paths() 299 if wx is None: 300 return True 301 302 # retry with wxPython 303 _log.debug('re-detecting paths with wxPython') 304 305 std_paths = wx.StandardPaths.Get() 306 _log.info('wxPython app name is [%s]', wx.GetApp().GetAppName()) 307 308 # user-specific config dir, usually below the home dir 309 mkdir(os.path.join(std_paths.GetUserConfigDir(), '.%s' % app_name)) 310 self.user_config_dir = os.path.join(std_paths.GetUserConfigDir(), '.%s' % app_name) 311 312 # system-wide config dir, usually below /etc/ under UN*X 313 try: 314 tmp = std_paths.GetConfigDir() 315 if not tmp.endswith(app_name): 316 tmp = os.path.join(tmp, app_name) 317 self.system_config_dir = tmp 318 except ValueError: 319 # leave it at what it was from direct detection 320 pass 321 322 # system-wide application data dir 323 # Robin attests that the following doesn't always 324 # give sane values on Windows, so IFDEF it 325 if 'wxMSW' in wx.PlatformInfo: 326 _log.warning('this platform (wxMSW) sometimes returns a broken value for the system-wide application data dir') 327 else: 328 try: 329 self.system_app_data_dir = std_paths.GetDataDir() 330 except ValueError: 331 pass 332 333 self.__log_paths() 334 return True
335 #--------------------------------------
336 - def __log_paths(self):
337 _log.debug('sys.argv[0]: %s', sys.argv[0]) 338 _log.debug('local application base dir: %s', self.local_base_dir) 339 _log.debug('current working dir: %s', self.working_dir) 340 #_log.debug('user home dir: %s', os.path.expanduser('~')) 341 _log.debug('user home dir: %s', self.home_dir) 342 _log.debug('user-specific config dir: %s', self.user_config_dir) 343 _log.debug('system-wide config dir: %s', self.system_config_dir) 344 _log.debug('system-wide application data dir: %s', self.system_app_data_dir)
345 #-------------------------------------- 346 # properties 347 #--------------------------------------
348 - def _set_user_config_dir(self, path):
349 if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)): 350 msg = '[%s:user_config_dir]: invalid path [%s]' % (self.__class__.__name__, path) 351 _log.error(msg) 352 raise ValueError(msg) 353 self.__user_config_dir = path
354
355 - def _get_user_config_dir(self):
356 return self.__user_config_dir
357 358 user_config_dir = property(_get_user_config_dir, _set_user_config_dir) 359 #--------------------------------------
360 - def _set_system_config_dir(self, path):
361 if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)): 362 msg = '[%s:system_config_dir]: invalid path [%s]' % (self.__class__.__name__, path) 363 _log.error(msg) 364 raise ValueError(msg) 365 self.__system_config_dir = path
366
367 - def _get_system_config_dir(self):
368 return self.__system_config_dir
369 370 system_config_dir = property(_get_system_config_dir, _set_system_config_dir) 371 #--------------------------------------
372 - def _set_system_app_data_dir(self, path):
373 if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)): 374 msg = '[%s:system_app_data_dir]: invalid path [%s]' % (self.__class__.__name__, path) 375 _log.error(msg) 376 raise ValueError(msg) 377 self.__system_app_data_dir = path
378
379 - def _get_system_app_data_dir(self):
380 return self.__system_app_data_dir
381 382 system_app_data_dir = property(_get_system_app_data_dir, _set_system_app_data_dir) 383 #--------------------------------------
384 - def _set_home_dir(self, path):
385 raise ArgumentError('invalid to set home dir')
386
387 - def _get_home_dir(self):
388 if self.__home_dir is not None: 389 return self.__home_dir 390 391 tmp = os.path.expanduser('~') 392 if tmp == '~': 393 _log.error('this platform does not expand ~ properly') 394 try: 395 tmp = os.environ['USERPROFILE'] 396 except KeyError: 397 _log.error('cannot access $USERPROFILE in environment') 398 399 if not ( 400 os.access(tmp, os.R_OK) 401 and 402 os.access(tmp, os.X_OK) 403 and 404 os.access(tmp, os.W_OK) 405 ): 406 msg = '[%s:home_dir]: invalid path [%s]' % (self.__class__.__name__, tmp) 407 _log.error(msg) 408 raise ValueError(msg) 409 410 self.__home_dir = tmp 411 return self.__home_dir
412 413 home_dir = property(_get_home_dir, _set_home_dir)
414 #===========================================================================
415 -def send_mail(sender=None, receiver=None, message=None, server=None, auth=None, debug=False, subject=None, encoding='quoted-printable', attachments=None):
416 417 if message is None: 418 return False 419 420 message = message.lstrip().lstrip('\r\n').lstrip() 421 422 if sender is None: 423 sender = default_mail_sender 424 425 if receiver is None: 426 receiver = [default_mail_receiver] 427 428 if server is None: 429 server = default_mail_server 430 431 if subject is None: 432 subject = u'gmTools.py: send_mail() test' 433 434 msg = StringIO.StringIO() 435 writer = MimeWriter.MimeWriter(msg) 436 writer.addheader('To', u', '.join(receiver)) 437 writer.addheader('From', sender) 438 writer.addheader('Subject', subject[:50].replace('\r', '/').replace('\n', '/')) 439 writer.addheader('MIME-Version', '1.0') 440 441 writer.startmultipartbody('mixed') 442 443 # start with a text/plain part 444 part = writer.nextpart() 445 body = part.startbody('text/plain') 446 part.flushheaders() 447 body.write(message.encode(encoding)) 448 449 # now add the attachments 450 if attachments is not None: 451 for a in attachments: 452 filename = os.path.basename(a[0]) 453 try: 454 mtype = a[1] 455 encoding = a[2] 456 except IndexError: 457 mtype, encoding = mimetypes.guess_type(a[0]) 458 if mtype is None: 459 mtype = 'application/octet-stream' 460 encoding = 'base64' 461 elif mtype == 'text/plain': 462 encoding = 'quoted-printable' 463 else: 464 encoding = 'base64' 465 466 part = writer.nextpart() 467 part.addheader('Content-Transfer-Encoding', encoding) 468 body = part.startbody("%s; name=%s" % (mtype, filename)) 469 mimetools.encode(open(a[0], 'rb'), body, encoding) 470 471 writer.lastpart() 472 473 import smtplib 474 session = smtplib.SMTP(server) 475 session.set_debuglevel(debug) 476 if auth is not None: 477 session.login(auth['user'], auth['password']) 478 refused = session.sendmail(sender, receiver, msg.getvalue()) 479 session.quit() 480 msg.close() 481 if len(refused) != 0: 482 _log.error("refused recipients: %s" % refused) 483 return False 484 485 return True
486 #-------------------------------------------------------------------------------
487 -def send_mail_old(sender=None, receiver=None, message=None, server=None, auth=None, debug=False, subject=None, encoding='latin1'):
488 """Send an E-Mail. 489 490 <debug>: see smtplib.set_debuglevel() 491 <auth>: {'user': ..., 'password': ...} 492 <receiver>: a list of email addresses 493 """ 494 if message is None: 495 return False 496 message = message.lstrip().lstrip('\r\n').lstrip() 497 498 if sender is None: 499 sender = default_mail_sender 500 501 if receiver is None: 502 receiver = [default_mail_receiver] 503 504 if server is None: 505 server = default_mail_server 506 507 if subject is None: 508 subject = u'gmTools.py: send_mail() test' 509 510 body = u"""From: %s 511 To: %s 512 Subject: %s 513 514 %s 515 """ % (sender, u', '.join(receiver), subject[:50].replace('\r', '/').replace('\n', '/'), message) 516 517 import smtplib 518 session = smtplib.SMTP(server) 519 session.set_debuglevel(debug) 520 if auth is not None: 521 session.login(auth['user'], auth['password']) 522 refused = session.sendmail(sender, receiver, body.encode(encoding)) 523 session.quit() 524 if len(refused) != 0: 525 _log.error("refused recipients: %s" % refused) 526 return False 527 528 return True
529 #===========================================================================
530 -def mkdir(directory=None):
531 try: 532 os.makedirs(directory) 533 except OSError, e: 534 if (e.errno == 17) and not os.path.isdir(directory): 535 raise 536 return True
537 #---------------------------------------------------------------------------
538 -def get_unique_filename(prefix=None, suffix=None, tmp_dir=None):
539 """This introduces a race condition between the file.close() and 540 actually using the filename. 541 542 The file will not exist after calling this function. 543 """ 544 if tmp_dir is not None: 545 if ( 546 not os.access(tmp_dir, os.F_OK) 547 or 548 not os.access(tmp_dir, os.X_OK | os.W_OK) 549 ): 550 _log.info('cannot find temporary dir [%s], using system default', tmp_dir) 551 tmp_dir = None 552 553 kwargs = {'dir': tmp_dir} 554 555 if prefix is None: 556 kwargs['prefix'] = 'gnumed-' 557 else: 558 kwargs['prefix'] = prefix 559 560 if suffix is None: 561 kwargs['suffix'] = '.tmp' 562 else: 563 if not suffix.startswith('.'): 564 suffix = '.' + suffix 565 kwargs['suffix'] = suffix 566 567 f = tempfile.NamedTemporaryFile(**kwargs) 568 filename = f.name 569 f.close() 570 571 return filename
572 #===========================================================================
573 -def import_module_from_directory(module_path=None, module_name=None):
574 """Import a module from any location.""" 575 576 if module_path not in sys.path: 577 _log.info('appending to sys.path: [%s]' % module_path) 578 sys.path.append(module_path) 579 remove_path = True 580 else: 581 remove_path = False 582 583 if module_name.endswith('.py'): 584 module_name = module_name[:-3] 585 586 try: 587 module = __import__(module_name) 588 except StandardError: 589 _log.exception('cannot __import__() module [%s] from [%s]' % (module_name, module_path)) 590 sys.path.remove(module_path) 591 raise 592 593 _log.info('imported module [%s] as [%s]' % (module_name, module)) 594 if remove_path: 595 sys.path.remove(module_path) 596 597 return module
598 #=========================================================================== 599 # text related tools 600 #--------------------------------------------------------------------------- 601 _kB = 1024 602 _MB = 1024 * _kB 603 _GB = 1024 * _MB 604 _TB = 1024 * _GB 605 _PB = 1024 * _TB 606 #---------------------------------------------------------------------------
607 -def size2str(size=0, template='%s'):
608 if size == 1: 609 return template % _('1 Byte') 610 if size < 10 * _kB: 611 return template % _('%s Bytes') % size 612 if size < _MB: 613 return template % u'%.1f kB' % (float(size) / _kB) 614 if size < _GB: 615 return template % u'%.1f MB' % (float(size) / _MB) 616 if size < _TB: 617 return template % u'%.1f GB' % (float(size) / _GB) 618 if size < _PB: 619 return template % u'%.1f TB' % (float(size) / _TB) 620 return template % u'%.1f PB' % (float(size) / _PB)
621 #---------------------------------------------------------------------------
622 -def bool2subst(boolean=None, true_return=True, false_return=False, none_return=None):
623 if boolean is None: 624 return none_return 625 if boolean is True: 626 return true_return 627 if boolean is False: 628 return false_return 629 raise ValueError('bool2subst(): <boolean> arg must be either of True, False, None')
630 #---------------------------------------------------------------------------
631 -def bool2str(boolean=None, true_str='True', false_str='False'):
632 return bool2subst ( 633 boolean = bool(boolean), 634 true_return = true_str, 635 false_return = false_str 636 )
637 #---------------------------------------------------------------------------
638 -def none_if(value=None, none_equivalent=None):
639 """Modelled after the SQL NULLIF function.""" 640 if value == none_equivalent: 641 return None 642 return value
643 #---------------------------------------------------------------------------
644 -def coalesce(initial=None, instead=None, template_initial=None, template_instead=None, none_equivalents=None):
645 """Modelled after the SQL coalesce function. 646 647 To be used to simplify constructs like: 648 649 if initial is None (or in none_equivalents): 650 real_value = (template_instead % instead) or instead 651 else: 652 real_value = (template_initial % initial) or initial 653 print real_value 654 655 @param initial: the value to be tested for <None> 656 @type initial: any Python type, must have a __str__ method if template_initial is not None 657 @param instead: the value to be returned if <initial> is None 658 @type instead: any Python type, must have a __str__ method if template_instead is not None 659 @param template_initial: if <initial> is returned replace the value into this template, must contain one <%s> 660 @type template_initial: string or None 661 @param template_instead: if <instead> is returned replace the value into this template, must contain one <%s> 662 @type template_instead: string or None 663 664 Ideas: 665 - list of insteads: initial, [instead, template], [instead, template], [instead, template], template_initial, ... 666 """ 667 if none_equivalents is None: 668 none_equivalents = [None] 669 670 if initial in none_equivalents: 671 672 if template_instead is None: 673 return instead 674 675 return template_instead % instead 676 677 if template_initial is None: 678 return initial 679 680 try: 681 return template_initial % initial 682 except TypeError: 683 return template_initial
684 #---------------------------------------------------------------------------
685 -def __cap_name(match_obj=None):
686 val = match_obj.group(0).lower() 687 if val in ['von', 'van', 'de', 'la', 'l', 'der', 'den']: # FIXME: this needs to expand, configurable ? 688 return val 689 buf = list(val) 690 buf[0] = buf[0].upper() 691 for part in ['mac', 'mc', 'de', 'la']: 692 if len(val) > len(part) and val[:len(part)] == part: 693 buf[len(part)] = buf[len(part)].upper() 694 return ''.join(buf)
695 #---------------------------------------------------------------------------
696 -def capitalize(text=None, mode=CAPS_NAMES):
697 """Capitalize the first character but leave the rest alone. 698 699 Note that we must be careful about the locale, this may 700 have issues ! However, for UTF strings it should just work. 701 """ 702 if (mode is None) or (mode == CAPS_NONE): 703 return text 704 705 if mode == CAPS_FIRST: 706 if len(text) == 1: 707 return text[0].upper() 708 return text[0].upper() + text[1:] 709 710 if mode == CAPS_ALLCAPS: 711 return text.upper() 712 713 if mode == CAPS_FIRST_ONLY: 714 if len(text) == 1: 715 return text[0].upper() 716 return text[0].upper() + text[1:].lower() 717 718 if mode == CAPS_WORDS: 719 return regex.sub(ur'(\w)(\w+)', lambda x: x.group(1).upper() + x.group(2).lower(), text) 720 721 if mode == CAPS_NAMES: 722 #return regex.sub(r'\w+', __cap_name, text) 723 return capitalize(text=text, mode=CAPS_FIRST) # until fixed 724 725 print "ERROR: invalid capitalization mode: [%s], leaving input as is" % mode 726 return text
727 #---------------------------------------------------------------------------
728 -def input2decimal(initial=None):
729 730 val = initial 731 732 # float ? -> to string first 733 if type(val) == type(1.4): 734 val = str(val) 735 736 # string ? -> "," to "." 737 if isinstance(val, basestring): 738 val = val.replace(',', '.', 1) 739 val = val.strip() 740 # val = val.lstrip('0') 741 # if val.startswith('.'): 742 # val = '0' + val 743 744 try: 745 d = decimal.Decimal(val) 746 return True, d 747 except (TypeError, decimal.InvalidOperation): 748 return False, val
749 #---------------------------------------------------------------------------
750 -def wrap(text=None, width=None, initial_indent=u'', subsequent_indent=u'', eol=u'\n'):
751 """A word-wrap function that preserves existing line breaks 752 and most spaces in the text. Expects that existing line 753 breaks are posix newlines (\n). 754 """ 755 wrapped = initial_indent + reduce ( 756 lambda line, word, width=width: '%s%s%s' % ( 757 line, 758 ' \n'[(len(line) - line.rfind('\n') - 1 + len(word.split('\n',1)[0]) >= width)], 759 word 760 ), 761 text.split(' ') 762 ) 763 764 if subsequent_indent != u'': 765 wrapped = (u'\n%s' % subsequent_indent).join(wrapped.split('\n')) 766 767 if eol != u'\n': 768 wrapped = wrapped.replace('\n', eol) 769 770 return wrapped
771 #---------------------------------------------------------------------------
772 -def unwrap(text=None, max_length=None, strip_whitespace=True, remove_empty_lines=True, line_separator = u' // '):
773 774 text = text.replace(u'\r', u'') 775 lines = text.split(u'\n') 776 text = u'' 777 for line in lines: 778 779 if strip_whitespace: 780 line = line.strip().strip(u'\t').strip() 781 782 if remove_empty_lines: 783 if line == u'': 784 continue 785 786 text += (u'%s%s' % (line, line_separator)) 787 788 text = text.rstrip(line_separator) 789 790 if max_length is not None: 791 text = text[:max_length] 792 793 text = text.rstrip(line_separator) 794 795 return text
796 #---------------------------------------------------------------------------
797 -def tex_escape_string(text=None):
798 """check for special latex-characters and transform them""" 799 800 text = text.replace(u'\\', u'$\\backslash$') 801 text = text.replace(u'{', u'\\{') 802 text = text.replace(u'}', u'\\}') 803 text = text.replace(u'%', u'\\%') 804 text = text.replace(u'&', u'\\&') 805 text = text.replace(u'#', u'\\#') 806 text = text.replace(u'$', u'\\$') 807 text = text.replace(u'_', u'\\_') 808 809 text = text.replace(u'^', u'\\verb#^#') 810 text = text.replace('~','\\verb#~#') 811 812 return text
813 #=========================================================================== 814 # main 815 #--------------------------------------------------------------------------- 816 if __name__ == '__main__': 817 818 #-----------------------------------------------------------------------
819 - def test_input2decimal():
820 821 tests = [ 822 [None, False], 823 824 ['', False], 825 [' 0 ', True, 0], 826 827 [0, True, 0], 828 [0.0, True, 0], 829 [.0, True, 0], 830 ['0', True, 0], 831 ['0.0', True, 0], 832 ['0,0', True, 0], 833 ['00.0', True, 0], 834 ['.0', True, 0], 835 [',0', True, 0], 836 837 [0.1, True, decimal.Decimal('0.1')], 838 [.01, True, decimal.Decimal('0.01')], 839 ['0.1', True, decimal.Decimal('0.1')], 840 ['0,1', True, decimal.Decimal('0.1')], 841 ['00.1', True, decimal.Decimal('0.1')], 842 ['.1', True, decimal.Decimal('0.1')], 843 [',1', True, decimal.Decimal('0.1')], 844 845 [1, True, 1], 846 [1.0, True, 1], 847 ['1', True, 1], 848 ['1.', True, 1], 849 ['1,', True, 1], 850 ['1.0', True, 1], 851 ['1,0', True, 1], 852 ['01.0', True, 1], 853 ['01,0', True, 1], 854 [' 01, ', True, 1], 855 ] 856 for test in tests: 857 conversion_worked, result = input2decimal(initial = test[0]) 858 859 expected2work = test[1] 860 861 if conversion_worked: 862 if expected2work: 863 if result == test[2]: 864 continue 865 else: 866 print "ERROR (conversion result wrong): >%s<, expected >%s<, got >%s<" % (test[0], test[2], result) 867 else: 868 print "ERROR (conversion worked but was expected to fail): >%s<, got >%s<" % (test[0], result) 869 else: 870 if not expected2work: 871 continue 872 else: 873 print "ERROR (conversion failed but was expected to work): >%s<, expected >%s<" % (test[0], test[2])
874 #-----------------------------------------------------------------------
875 - def test_coalesce():
876 print 'testing coalesce()' 877 print "------------------" 878 tests = [ 879 [None, 'something other than <None>', None, None, 'something other than <None>'], 880 ['Captain', 'Mr.', '%s.'[:4], 'Mr.', 'Capt.'], 881 ['value to test', 'test 3 failed', 'template with "%s" included', None, 'template with "value to test" included'], 882 ['value to test', 'test 4 failed', 'template with value not included', None, 'template with value not included'], 883 [None, 'initial value was None', 'template_initial: %s', None, 'initial value was None'], 884 [None, 'initial value was None', 'template_initial: %%(abc)s', None, 'initial value was None'] 885 ] 886 passed = True 887 for test in tests: 888 result = coalesce ( 889 initial = test[0], 890 instead = test[1], 891 template_initial = test[2], 892 template_instead = test[3] 893 ) 894 if result != test[4]: 895 print "ERROR" 896 print "coalesce: (%s, %s, %s, %s)" % (test[0], test[1], test[2], test[3]) 897 print "expected:", test[4] 898 print "received:", result 899 passed = False 900 901 if passed: 902 print "passed" 903 else: 904 print "failed" 905 return passed
906 #-----------------------------------------------------------------------
907 - def test_capitalize():
908 print 'testing capitalize() ...' 909 success = True 910 pairs = [ 911 # [original, expected result, CAPS mode] 912 [u'Boot', u'Boot', CAPS_FIRST_ONLY], 913 [u'boot', u'Boot', CAPS_FIRST_ONLY], 914 [u'booT', u'Boot', CAPS_FIRST_ONLY], 915 [u'BoOt', u'Boot', CAPS_FIRST_ONLY], 916 [u'boots-Schau', u'Boots-Schau', CAPS_WORDS], 917 [u'boots-sChau', u'Boots-Schau', CAPS_WORDS], 918 [u'boot camp', u'Boot Camp', CAPS_WORDS], 919 [u'fahrner-Kampe', u'Fahrner-Kampe', CAPS_NAMES], 920 [u'häkkönen', u'Häkkönen', CAPS_NAMES], 921 [u'McBurney', u'McBurney', CAPS_NAMES], 922 [u'mcBurney', u'McBurney', CAPS_NAMES], 923 [u'blumberg', u'Blumberg', CAPS_NAMES], 924 [u'roVsing', u'RoVsing', CAPS_NAMES], 925 [u'Özdemir', u'Özdemir', CAPS_NAMES], 926 [u'özdemir', u'Özdemir', CAPS_NAMES], 927 ] 928 for pair in pairs: 929 result = capitalize(pair[0], pair[2]) 930 if result != pair[1]: 931 success = False 932 print 'ERROR (caps mode %s): "%s" -> "%s", expected "%s"' % (pair[2], pair[0], result, pair[1]) 933 934 if success: 935 print "... SUCCESS" 936 937 return success
938 #-----------------------------------------------------------------------
939 - def test_import_module():
940 print "testing import_module_from_directory()" 941 path = sys.argv[1] 942 name = sys.argv[2] 943 try: 944 mod = import_module_from_directory(module_path = path, module_name = name) 945 except: 946 print "module import failed, see log" 947 return False 948 949 print "module import succeeded", mod 950 print dir(mod) 951 return True
952 #-----------------------------------------------------------------------
953 - def test_mkdir():
954 print "testing mkdir()" 955 mkdir(sys.argv[1])
956 #-----------------------------------------------------------------------
957 - def test_send_mail():
958 msg = u""" 959 To: %s 960 From: %s 961 Subject: gmTools test suite mail 962 963 This is a test mail from the gmTools.py module. 964 """ % (default_mail_receiver, default_mail_sender) 965 print "mail sending succeeded:", send_mail ( 966 receiver = [default_mail_receiver, u'karsten.hilbert@gmx.net'], 967 message = msg, 968 auth = {'user': default_mail_sender, 'password': u'gnumed-at-gmx-net'}, # u'gm/bugs/gmx' 969 debug = True, 970 attachments = [sys.argv[0]] 971 )
972 #-----------------------------------------------------------------------
973 - def test_gmPaths():
974 print "testing gmPaths()" 975 print "-----------------" 976 paths = gmPaths(wx=None, app_name='gnumed') 977 print "user config dir:", paths.user_config_dir 978 print "system config dir:", paths.system_config_dir 979 print "local base dir:", paths.local_base_dir 980 print "system app data dir:", paths.system_app_data_dir 981 print "working directory :", paths.working_dir
982 #-----------------------------------------------------------------------
983 - def test_none_if():
984 print "testing none_if()" 985 print "-----------------" 986 tests = [ 987 [None, None, None], 988 ['a', 'a', None], 989 ['a', 'b', 'a'], 990 ['a', None, 'a'], 991 [None, 'a', None], 992 [1, 1, None], 993 [1, 2, 1], 994 [1, None, 1], 995 [None, 1, None] 996 ] 997 998 for test in tests: 999 if none_if(value = test[0], none_equivalent = test[1]) != test[2]: 1000 print 'ERROR: none_if(%s) returned [%s], expected [%s]' % (test[0], none_if(test[0], test[1]), test[2]) 1001 1002 return True
1003 #-----------------------------------------------------------------------
1004 - def test_bool2str():
1005 tests = [ 1006 [True, 'Yes', 'Yes', 'Yes'], 1007 [False, 'OK', 'not OK', 'not OK'] 1008 ] 1009 for test in tests: 1010 if bool2str(test[0], test[1], test[2]) != test[3]: 1011 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]) 1012 1013 return True
1014 #-----------------------------------------------------------------------
1015 - def test_bool2subst():
1016 1017 print bool2subst(True, 'True', 'False', 'is None') 1018 print bool2subst(False, 'True', 'False', 'is None') 1019 print bool2subst(None, 'True', 'False', 'is None')
1020 #-----------------------------------------------------------------------
1021 - def test_get_unique_filename():
1022 print get_unique_filename() 1023 print get_unique_filename(prefix='test-') 1024 print get_unique_filename(suffix='tst') 1025 print get_unique_filename(prefix='test-', suffix='tst') 1026 print get_unique_filename(tmp_dir='/home/ncq/Archiv/')
1027 #-----------------------------------------------------------------------
1028 - def test_size2str():
1029 print "testing size2str()" 1030 print "------------------" 1031 tests = [0, 1, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000, 1000000000000, 10000000000000] 1032 for test in tests: 1033 print size2str(test)
1034 #-----------------------------------------------------------------------
1035 - def test_unwrap():
1036 1037 test = """ 1038 second line\n 1039 3rd starts with tab \n 1040 4th with a space \n 1041 1042 6th 1043 1044 """ 1045 print unwrap(text = test, max_length = 25)
1046 #-----------------------------------------------------------------------
1047 - def test_wrap():
1048 test = 'line 1\nline 2\nline 3' 1049 1050 print "wrap 5-6-7 initial 0, subsequent 0" 1051 print wrap(test, 5) 1052 print 1053 print wrap(test, 6) 1054 print 1055 print wrap(test, 7) 1056 print "-------" 1057 raw_input() 1058 print "wrap 5 initial 1-1-3, subsequent 1-3-1" 1059 print wrap(test, 5, u' ', u' ') 1060 print 1061 print wrap(test, 5, u' ', u' ') 1062 print 1063 print wrap(test, 5, u' ', u' ') 1064 print "-------" 1065 raw_input() 1066 print "wrap 6 initial 1-1-3, subsequent 1-3-1" 1067 print wrap(test, 6, u' ', u' ') 1068 print 1069 print wrap(test, 6, u' ', u' ') 1070 print 1071 print wrap(test, 6, u' ', u' ') 1072 print "-------" 1073 raw_input() 1074 print "wrap 7 initial 1-1-3, subsequent 1-3-1" 1075 print wrap(test, 7, u' ', u' ') 1076 print 1077 print wrap(test, 7, u' ', u' ') 1078 print 1079 print wrap(test, 7, u' ', u' ')
1080 #-----------------------------------------------------------------------
1081 - def test_check_for_update():
1082 1083 test_data = [ 1084 ('http://www.gnumed.de/downloads/gnumed-versions.txt', None, None, False), 1085 ('file:///home/ncq/gm-versions.txt', None, None, False), 1086 ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.1', False), 1087 ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.1', True), 1088 ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.5', True) 1089 ] 1090 1091 for test in test_data: 1092 print "arguments:", test 1093 found, msg = check_for_update(test[0], test[1], test[2], test[3]) 1094 print msg 1095 1096 return
1097 #----------------------------------------------------------------------- 1098 if len(sys.argv) > 1 and sys.argv[1] == 'test': 1099 1100 #test_check_for_update() 1101 #test_coalesce() 1102 #test_capitalize() 1103 #test_import_module() 1104 #test_mkdir() 1105 #test_send_mail() 1106 #test_gmPaths() 1107 #test_none_if() 1108 #test_bool2str() 1109 #test_bool2subst() 1110 #test_get_unique_filename() 1111 #test_size2str() 1112 #test_wrap() 1113 #test_input2decimal() 1114 test_unwrap() 1115 1116 #=========================================================================== 1117 # $Log: gmTools.py,v $ 1118 # Revision 1.98 2010/01/17 19:47:10 ncq 1119 # - add comment on quotes 1120 # 1121 # Revision 1.97 2010/01/15 12:42:46 ncq 1122 # - tex-escape-string 1123 # 1124 # Revision 1.96 2009/12/21 15:02:51 ncq 1125 # - cleanup 1126 # 1127 # Revision 1.95 2009/11/29 15:57:51 ncq 1128 # - must properly initialize is_dict_reader 1129 # 1130 # Revision 1.94 2009/11/15 01:04:30 ncq 1131 # - add smiling/frowning face 1132 # 1133 # Revision 1.93 2009/11/06 15:12:57 ncq 1134 # - add right arrow 1135 # 1136 # Revision 1.92 2009/10/28 16:40:32 ncq 1137 # - add infinity symbol 1138 # 1139 # Revision 1.91 2009/10/21 20:39:18 ncq 1140 # - make unicode csv *dict* reader actually work 1141 # - make intermediate encoding of unicode csv reader configurable 1142 # 1143 # Revision 1.90 2009/09/23 14:32:30 ncq 1144 # - u-corresponds-to 1145 # 1146 # Revision 1.89 2009/09/08 17:15:13 ncq 1147 # - add unwrap() test 1148 # 1149 # Revision 1.88 2009/09/01 22:25:02 ncq 1150 # - enhance coalesce with none_equivalents 1151 # 1152 # Revision 1.87 2009/08/13 12:12:20 ncq 1153 # - slightly better upgrade available message 1154 # 1155 # Revision 1.86 2009/07/15 12:17:14 ncq 1156 # - add latin cross unicode point 1157 # - better error handling on version checking 1158 # 1159 # Revision 1.85 2009/06/10 21:00:43 ncq 1160 # - remove "gm_versions" cfg source after use 1161 # 1162 # Revision 1.84 2009/05/13 10:35:22 ncq 1163 # - some cleanup 1164 # 1165 # Revision 1.83 2009/04/21 16:54:34 ncq 1166 # - fix setting sys app data dir on non-Windows 1167 # 1168 # Revision 1.82 2009/04/19 22:26:25 ncq 1169 # - factor out LOINC handling 1170 # - factor out interval parsing 1171 # 1172 # Revision 1.81 2009/04/14 17:54:48 ncq 1173 # - test under Py2.6 1174 # 1175 # Revision 1.80 2009/04/03 12:29:36 ncq 1176 # - add attachment handling to send_mail 1177 # 1178 # Revision 1.79 2009/04/03 11:08:33 ncq 1179 # - add two more unicode code points 1180 # 1181 # Revision 1.78 2009/04/03 09:37:05 ncq 1182 # - splitter for LOINCDB.TXT 1183 # - improved paths handling (~ doesn't expand under Wine) 1184 # 1185 # Revision 1.77 2009/03/18 14:29:45 ncq 1186 # - add unicode code point for 3/4 1187 # - better fallback for sys app data dir on Windows 1188 # 1189 # Revision 1.76 2009/03/10 14:22:33 ncq 1190 # - add unicode code points 1191 # 1192 # Revision 1.75 2009/03/01 18:10:50 ncq 1193 # - improve update-avail message 1194 # 1195 # Revision 1.74 2009/02/18 13:45:25 ncq 1196 # - get_unique_filename API change 1197 # 1198 # Revision 1.73 2009/01/02 11:38:09 ncq 1199 # - input2decimal + tests 1200 # 1201 # Revision 1.72 2008/12/22 18:58:53 ncq 1202 # - cleanup 1203 # 1204 # Revision 1.71 2008/12/09 23:28:15 ncq 1205 # - better logging 1206 # - always remove aux module path if importing fails 1207 # 1208 # Revision 1.70 2008/11/20 18:47:40 ncq 1209 # - add left arrow unicode 1210 # - fix logging in update check 1211 # 1212 # Revision 1.69 2008/11/03 10:28:55 ncq 1213 # - check_for_update 1214 # - improved logging and wording 1215 # - logic reversal fix 1216 # 1217 # Revision 1.68 2008/10/12 15:48:33 ncq 1218 # - improved wording when checking for updates 1219 # 1220 # Revision 1.67 2008/08/31 16:13:15 ncq 1221 # - cleanup 1222 # 1223 # Revision 1.66 2008/08/28 18:32:24 ncq 1224 # - read latest branch then latest release from branch group 1225 # 1226 # Revision 1.65 2008/08/20 13:53:57 ncq 1227 # - add some coalesce tests 1228 # 1229 # Revision 1.64 2008/07/28 15:43:35 ncq 1230 # - teach wrap() about target EOL 1231 # 1232 # Revision 1.63 2008/07/12 15:30:56 ncq 1233 # - improved coalesce test 1234 # 1235 # Revision 1.62 2008/07/12 15:24:37 ncq 1236 # - impove coalesce to allow template_initial to be returned *instead* of 1237 # initial substituted into the template by not including a substitution 1238 # 1239 # Revision 1.61 2008/07/10 20:51:38 ncq 1240 # - better logging 1241 # 1242 # Revision 1.60 2008/07/10 19:59:09 ncq 1243 # - better logging 1244 # - check whether sys config dir ends in "gnumed" 1245 # 1246 # Revision 1.59 2008/07/07 11:34:41 ncq 1247 # - robustify capsify on single character strings 1248 # 1249 # Revision 1.58 2008/06/28 18:25:01 ncq 1250 # - add unicode Registered TM symbol 1251 # 1252 # Revision 1.57 2008/05/31 16:32:42 ncq 1253 # - a couple of unicode shortcuts 1254 # 1255 # Revision 1.56 2008/05/26 12:05:50 ncq 1256 # - improved wording of update message 1257 # - better handling of CVS tip 1258 # 1259 # Revision 1.55 2008/05/21 15:51:45 ncq 1260 # - if cannot open update URL may throw OSError, so deal with that 1261 # 1262 # Revision 1.54 2008/05/21 14:01:32 ncq 1263 # - add check_for_update and tests 1264 # 1265 # Revision 1.53 2008/05/13 14:09:36 ncq 1266 # - str2interval: support xMxW syntax 1267 # 1268 # Revision 1.52 2008/05/07 15:18:01 ncq 1269 # - i18n str2interval 1270 # 1271 # Revision 1.51 2008/04/16 20:34:43 ncq 1272 # - add bool2subst tests 1273 # 1274 # Revision 1.50 2008/04/11 12:24:39 ncq 1275 # - add initial_indent/subsequent_indent and tests to wrap() 1276 # 1277 # Revision 1.49 2008/03/20 15:29:51 ncq 1278 # - bool2subst() supporting None, make bool2str() use it 1279 # 1280 # Revision 1.48 2008/03/02 15:10:32 ncq 1281 # - truncate exception comment to 50 chars when used as subject 1282 # 1283 # Revision 1.47 2008/01/16 19:42:24 ncq 1284 # - whitespace sync 1285 # 1286 # Revision 1.46 2007/12/23 11:59:40 ncq 1287 # - improved docs 1288 # 1289 # Revision 1.45 2007/12/12 16:24:09 ncq 1290 # - general cleanup 1291 # 1292 # Revision 1.44 2007/12/11 14:33:48 ncq 1293 # - use standard logging module 1294 # 1295 # Revision 1.43 2007/11/28 13:59:23 ncq 1296 # - test improved 1297 # 1298 # Revision 1.42 2007/11/21 13:28:35 ncq 1299 # - enhance send_mail() with subject and encoding 1300 # - handle body formatting 1301 # 1302 # Revision 1.41 2007/10/23 21:23:30 ncq 1303 # - cleanup 1304 # 1305 # Revision 1.40 2007/10/09 10:29:02 ncq 1306 # - clean up import_module_from_directory() 1307 # 1308 # Revision 1.39 2007/10/08 12:48:17 ncq 1309 # - normalize / and \ in import_module_from_directory() so it works on Windows 1310 # 1311 # Revision 1.38 2007/08/29 14:33:56 ncq 1312 # - better document get_unique_filename() 1313 # 1314 # Revision 1.37 2007/08/28 21:47:19 ncq 1315 # - log user home dir 1316 # 1317 # Revision 1.36 2007/08/15 09:18:56 ncq 1318 # - size2str() and test 1319 # 1320 # Revision 1.35 2007/08/07 21:41:02 ncq 1321 # - cPaths -> gmPaths 1322 # 1323 # Revision 1.34 2007/07/13 09:47:38 ncq 1324 # - fix and test suite for get_unique_filename() 1325 # 1326 # Revision 1.33 2007/07/11 21:06:51 ncq 1327 # - improved docs 1328 # - get_unique_filename() 1329 # 1330 # Revision 1.32 2007/07/10 20:45:42 ncq 1331 # - add unicode CSV reader 1332 # - factor out OOo related code 1333 # 1334 # Revision 1.31 2007/06/19 12:43:17 ncq 1335 # - add bool2str() and test 1336 # 1337 # Revision 1.30 2007/06/10 09:56:03 ncq 1338 # - u''ificiation and flags in regex calls 1339 # 1340 # Revision 1.29 2007/05/17 15:12:59 ncq 1341 # - even more careful about pathes 1342 # 1343 # Revision 1.28 2007/05/17 15:10:16 ncq 1344 # - create user config dir if it doesn't exist 1345 # 1346 # Revision 1.27 2007/05/15 08:20:13 ncq 1347 # - ifdef GetDataDir() on wxMSW as per Robin's suggestion 1348 # 1349 # Revision 1.26 2007/05/14 08:35:06 ncq 1350 # - better logging 1351 # - try to handle platforms with broken GetDataDir() 1352 # 1353 # Revision 1.25 2007/05/13 21:20:54 ncq 1354 # - improved logging 1355 # 1356 # Revision 1.24 2007/05/13 20:22:17 ncq 1357 # - log errors 1358 # 1359 # Revision 1.23 2007/05/08 16:03:55 ncq 1360 # - add console exception display handler 1361 # 1362 # Revision 1.22 2007/05/07 12:31:06 ncq 1363 # - improved path handling and testing 1364 # - switch file to utf8 1365 # 1366 # Revision 1.21 2007/04/21 19:38:27 ncq 1367 # - add none_if() and test suite 1368 # 1369 # Revision 1.20 2007/04/19 13:09:52 ncq 1370 # - add cPaths borg and test suite 1371 # 1372 # Revision 1.19 2007/04/09 16:30:31 ncq 1373 # - add send_mail() 1374 # 1375 # Revision 1.18 2007/03/08 16:19:30 ncq 1376 # - typo and cleanup 1377 # 1378 # Revision 1.17 2007/02/17 13:58:11 ncq 1379 # - improved coalesce() 1380 # 1381 # Revision 1.16 2007/02/04 16:43:01 ncq 1382 # - improve capitalize() test suite 1383 # - set coding 1384 # 1385 # Revision 1.15 2007/02/04 16:29:51 ncq 1386 # - make umlauts u'' 1387 # 1388 # Revision 1.14 2007/02/04 15:33:28 ncq 1389 # - enhance capitalize() and add mode CONSTS for it 1390 # - however, CAPS_NAMES for now maps to CAPS_FIRST until fixed for Heller-Brunner 1391 # - slightly improved test suite for it 1392 # 1393 # Revision 1.13 2007/01/30 17:38:28 ncq 1394 # - add mkdir() and a test for it 1395 # 1396 # Revision 1.12 2007/01/20 22:04:01 ncq 1397 # - strip ".py" from script name if it is there 1398 # 1399 # Revision 1.11 2007/01/18 12:46:30 ncq 1400 # - add reasonably safe import_module_from_directory() and test 1401 # 1402 # Revision 1.10 2007/01/15 20:20:39 ncq 1403 # - add wrap() 1404 # 1405 # Revision 1.9 2007/01/06 17:05:57 ncq 1406 # - start OOo server if cannot connect to one 1407 # - test suite 1408 # 1409 # Revision 1.8 2006/12/21 10:53:53 ncq 1410 # - document coalesce() better 1411 # 1412 # Revision 1.7 2006/12/18 15:51:12 ncq 1413 # - comment how to start server OOo writer 1414 # 1415 # Revision 1.6 2006/12/17 20:47:16 ncq 1416 # - add open_uri_in_ooo() 1417 # 1418 # Revision 1.5 2006/11/27 23:02:08 ncq 1419 # - add comment 1420 # 1421 # Revision 1.4 2006/11/24 09:52:09 ncq 1422 # - add str2interval() - this will need to end up in an interval input phrasewheel ! 1423 # - improve test suite 1424 # 1425 # Revision 1.3 2006/11/20 15:58:10 ncq 1426 # - template handling in coalesce() 1427 # 1428 # Revision 1.2 2006/10/31 16:03:06 ncq 1429 # - add capitalize() and test 1430 # 1431 # Revision 1.1 2006/09/03 08:53:19 ncq 1432 # - first version 1433 # 1434 # 1435