Gnumed.pycommon.gmTools

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  __author__ = "K. Hilbert <Karsten.Hilbert@gmx.net>"
       6  __license__ = "GPL v2 or later (details at http://www.gnu.org)"
       7
       8  # std libs
       9  import re as regex, sys, os, os.path, csv, tempfile, logging, hashlib
      10  import platform
      11  import subprocess
      12  import decimal
      13  import cPickle, zlib
      14  import xml.sax.saxutils as xml_tools
      15
      16
      17  # GNUmed libs
      18  if __name__ == '__main__':
      19          # for testing:
      20          logging.basicConfig(level = logging.DEBUG)
      21          sys.path.insert(0, '../../')
      22          from Gnumed.pycommon import gmI18N
      23          gmI18N.activate_locale()
      24          gmI18N.install_domain()
      25
      26  from Gnumed.pycommon import gmBorg
      27
      28
      29  _log = logging.getLogger('gm.tools')
      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
      41  u_currency_pound = u'\u00A3'                            # Pound sign
      42  u_currency_sign = u'\u00A4'                                     # generic currency sign
      43  u_currency_yen = u'\u00A5'                                      # Yen sign
      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_multiply = u'\u00D7'                                          # x
      52  u_greek_ALPHA = u'\u0391'
      53  u_greek_alpha = u'\u03b1'
      54  u_greek_OMEGA = u'\u03A9'
      55  u_greek_omega = u'\u03c9'
      56  u_triangular_bullet = u'\u2023'                         # triangular bullet  (>)
      57  u_ellipsis = u'\u2026'                                          # ...
      58  u_euro = u'\u20AC'                                                      # EURO sign
      59  u_numero = u'\u2116'                                            # No. / # sign
      60  u_down_left_arrow = u'\u21B5'                           # <-'
      61  u_left_arrow = u'\u2190'                                        # <--
      62  u_right_arrow = u'\u2192'                                       # -->
      63  u_left_arrow_with_tail = u'\u21a2'                      # <--<
      64  u_sum = u'\u2211'                                                       # sigma
      65  u_almost_equal_to = u'\u2248'                           # approximately / nearly / roughly
      66  u_corresponds_to = u'\u2258'
      67  u_infinity = u'\u221E'
      68  u_diameter = u'\u2300'
      69  u_checkmark_crossed_out = u'\u237B'
      70  u_box_horiz_single = u'\u2500'
      71  u_box_horiz_4dashes = u'\u2508'
      72  u_box_top_double = u'\u2550'
      73  u_box_top_left_double_single = u'\u2552'
      74  u_box_top_right_double_single = u'\u2555'
      75  u_box_top_left_arc = u'\u256d'
      76  u_box_bottom_right_arc = u'\u256f'
      77  u_box_bottom_left_arc = u'\u2570'
      78  u_box_horiz_light_heavy = u'\u257c'
      79  u_box_horiz_heavy_light = u'\u257e'
      80  u_skull_and_crossbones = u'\u2620'
      81  u_frowning_face = u'\u2639'
      82  u_smiling_face = u'\u263a'
      83  u_black_heart = u'\u2665'
      84  u_checkmark_thin = u'\u2713'
      85  u_checkmark_thick = u'\u2714'
      86  u_writing_hand = u'\u270d'
      87  u_pencil_1 = u'\u270e'
      88  u_pencil_2 = u'\u270f'
      89  u_pencil_3 = u'\u2710'
      90  u_latin_cross = u'\u271d'
      91  u_kanji_yen = u'\u5186'                                         # Yen kanji
      92  u_replacement_character = u'\ufffd'
      93  u_link_symbol = u'\u1f517'
      94
      95  #===========================================================================
      96 -def handle_uncaught_exception_console(t, v, tb):
      97
      98          print ".========================================================"
      99          print "| Unhandled exception caught !"
     100          print "| Type :", t
     101          print "| Value:", v
     102          print "`========================================================"
     103          _log.critical('unhandled exception caught', exc_info = (t,v,tb))
     104          sys.__excepthook__(t,v,tb)
     105  #===========================================================================
     106  # path level operations
     107  #---------------------------------------------------------------------------
     108 -def mkdir(directory=None):
     109          try:
     110                  os.makedirs(directory)
     111          except OSError, e:
     112                  if (e.errno == 17) and not os.path.isdir(directory):
     113                          raise
     114          return True
     115
     116  #---------------------------------------------------------------------------
     117 -class gmPaths(gmBorg.cBorg):
     118          """This class provides the following paths:
     119
     120          .home_dir
     121          .local_base_dir
     122          .working_dir
     123          .user_config_dir
     124          .system_config_dir
     125          .system_app_data_dir
     126          .tmp_dir (readonly)
     127          """
     128 -        def __init__(self, app_name=None, wx=None):
     129                  """Setup pathes.
     130
     131                  <app_name> will default to (name of the script - .py)
     132                  """
     133                  try:
     134                          self.already_inited
     135                          return
     136                  except AttributeError:
     137                          pass
     138
     139                  self.init_paths(app_name=app_name, wx=wx)
     140                  self.already_inited = True
     141          #--------------------------------------
     142          # public API
     143          #--------------------------------------
     144 -        def init_paths(self, app_name=None, wx=None):
     145
     146                  if wx is None:
     147                          _log.debug('wxPython not available')
     148                  _log.debug('detecting paths directly')
     149
     150                  if app_name is None:
     151                          app_name, ext = os.path.splitext(os.path.basename(sys.argv[0]))
     152                          _log.info('app name detected as [%s]', app_name)
     153                  else:
     154                          _log.info('app name passed in as [%s]', app_name)
     155
     156                  # the user home, doesn't work in Wine so work around that
     157                  self.__home_dir = None
     158
     159                  # where the main script (the "binary") is installed
     160                  if getattr(sys, 'frozen', False):
     161                          _log.info('frozen app, installed into temporary path')
     162                          # this would find the path of *THIS* file
     163                          #self.local_base_dir = os.path.dirname(__file__)
     164                          # while this is documented on the web, the ${_MEIPASS2} does not exist
     165                          #self.local_base_dir = os.environ.get('_MEIPASS2')
     166                          # this is what Martin Zibricky <mzibr.public@gmail.com> told us to use
     167                          # when asking about this on pyinstaller@googlegroups.com
     168                          #self.local_base_dir = sys._MEIPASS
     169                          # however, we are --onedir, so we should look at sys.executable
     170                          # as per the pyinstaller manual
     171                          self.local_base_dir = os.path.dirname(sys.executable)
     172                  else:
     173                          self.local_base_dir = os.path.abspath(os.path.dirname(sys.argv[0]))
     174
     175                  # the current working dir at the OS
     176                  self.working_dir = os.path.abspath(os.curdir)
     177
     178                  # user-specific config dir, usually below the home dir
     179                  mkdir(os.path.join(self.home_dir, '.%s' % app_name))
     180                  self.user_config_dir = os.path.join(self.home_dir, '.%s' % app_name)
     181
     182                  # system-wide config dir, usually below /etc/ under UN*X
     183                  try:
     184                          self.system_config_dir = os.path.join('/etc', app_name)
     185                  except ValueError:
     186                          #self.system_config_dir = self.local_base_dir
     187                          self.system_config_dir = self.user_config_dir
     188
     189                  # system-wide application data dir
     190                  try:
     191                          self.system_app_data_dir = os.path.join(sys.prefix, 'share', app_name)
     192                  except ValueError:
     193                          self.system_app_data_dir = self.local_base_dir
     194
     195                  # temporary directory
     196                  try:
     197                          self.__tmp_dir_already_set
     198                          _log.debug('temp dir already set')
     199                  except AttributeError:
     200                          tmp_base = os.path.join(tempfile.gettempdir(), app_name)
     201                          mkdir(tmp_base)
     202                          _log.info('previous temp dir: %s', tempfile.gettempdir())
     203                          tempfile.tempdir = tmp_base
     204                          _log.info('intermediate temp dir: %s', tempfile.gettempdir())
     205                          self.tmp_dir = tempfile.mkdtemp(prefix = r'gm-')
     206
     207                  self.__log_paths()
     208                  if wx is None:
     209                          return True
     210
     211                  # retry with wxPython
     212                  _log.debug('re-detecting paths with wxPython')
     213
     214                  std_paths = wx.StandardPaths.Get()
     215                  _log.info('wxPython app name is [%s]', wx.GetApp().GetAppName())
     216
     217                  # user-specific config dir, usually below the home dir
     218                  mkdir(os.path.join(std_paths.GetUserConfigDir(), '.%s' % app_name))
     219                  self.user_config_dir = os.path.join(std_paths.GetUserConfigDir(), '.%s' % app_name)
     220
     221                  # system-wide config dir, usually below /etc/ under UN*X
     222                  try:
     223                          tmp = std_paths.GetConfigDir()
     224                          if not tmp.endswith(app_name):
     225                                  tmp = os.path.join(tmp, app_name)
     226                          self.system_config_dir = tmp
     227                  except ValueError:
     228                          # leave it at what it was from direct detection
     229                          pass
     230
     231                  # system-wide application data dir
     232                  # Robin attests that the following doesn't always
     233                  # give sane values on Windows, so IFDEF it
     234                  if 'wxMSW' in wx.PlatformInfo:
     235                          _log.warning('this platform (wxMSW) sometimes returns a broken value for the system-wide application data dir')
     236                  else:
     237                          try:
     238                                  self.system_app_data_dir = std_paths.GetDataDir()
     239                          except ValueError:
     240                                  pass
     241
     242                  self.__log_paths()
     243                  return True
     244          #--------------------------------------
     245 -        def __log_paths(self):
     246                  _log.debug('sys.argv[0]: %s', sys.argv[0])
     247                  _log.debug('sys.executable: %s', sys.executable)
     248                  _log.debug('sys._MEIPASS: %s', getattr(sys, '_MEIPASS', '<not found>'))
     249                  _log.debug('os.environ["_MEIPASS2"]: %s', os.environ.get('_MEIPASS2', '<not found>'))
     250                  _log.debug('__file__ : %s', __file__)
     251                  _log.debug('local application base dir: %s', self.local_base_dir)
     252                  _log.debug('current working dir: %s', self.working_dir)
     253                  _log.debug('user home dir: %s', self.home_dir)
     254                  _log.debug('user-specific config dir: %s', self.user_config_dir)
     255                  _log.debug('system-wide config dir: %s', self.system_config_dir)
     256                  _log.debug('system-wide application data dir: %s', self.system_app_data_dir)
     257                  _log.debug('temporary dir: %s', self.tmp_dir)
     258          #--------------------------------------
     259          # properties
     260          #--------------------------------------
     261 -        def _set_user_config_dir(self, path):
     262                  if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)):
     263                          msg = '[%s:user_config_dir]: invalid path [%s]' % (self.__class__.__name__, path)
     264                          _log.error(msg)
     265                          raise ValueError(msg)
     266                  self.__user_config_dir = path
     267
     268 -        def _get_user_config_dir(self):
     269                  return self.__user_config_dir
     270
     271          user_config_dir = property(_get_user_config_dir, _set_user_config_dir)
     272          #--------------------------------------
     273 -        def _set_system_config_dir(self, path):
     274                  if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)):
     275                          msg = '[%s:system_config_dir]: invalid path [%s]' % (self.__class__.__name__, path)
     276                          _log.error(msg)
     277                          raise ValueError(msg)
     278                  self.__system_config_dir = path
     279
     280 -        def _get_system_config_dir(self):
     281                  return self.__system_config_dir
     282
     283          system_config_dir = property(_get_system_config_dir, _set_system_config_dir)
     284          #--------------------------------------
     285 -        def _set_system_app_data_dir(self, path):
     286                  if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)):
     287                          msg = '[%s:system_app_data_dir]: invalid path [%s]' % (self.__class__.__name__, path)
     288                          _log.error(msg)
     289                          raise ValueError(msg)
     290                  self.__system_app_data_dir = path
     291
     292 -        def _get_system_app_data_dir(self):
     293                  return self.__system_app_data_dir
     294
     295          system_app_data_dir = property(_get_system_app_data_dir, _set_system_app_data_dir)
     296          #--------------------------------------
     297 -        def _set_home_dir(self, path):
     298                  raise ValueError('invalid to set home dir')
     299
     300 -        def _get_home_dir(self):
     301                  if self.__home_dir is not None:
     302                          return self.__home_dir
     303
     304                  tmp = os.path.expanduser('~')
     305                  if tmp == '~':
     306                          _log.error('this platform does not expand ~ properly')
     307                          try:
     308                                  tmp = os.environ['USERPROFILE']
     309                          except KeyError:
     310                                  _log.error('cannot access $USERPROFILE in environment')
     311
     312                  if not (
     313                          os.access(tmp, os.R_OK)
     314                                  and
     315                          os.access(tmp, os.X_OK)
     316                                  and
     317                          os.access(tmp, os.W_OK)
     318                  ):
     319                          msg = '[%s:home_dir]: invalid path [%s]' % (self.__class__.__name__, tmp)
     320                          _log.error(msg)
     321                          raise ValueError(msg)
     322
     323                  self.__home_dir = tmp
     324                  return self.__home_dir
     325
     326          home_dir = property(_get_home_dir, _set_home_dir)
     327          #--------------------------------------
     328 -        def _set_tmp_dir(self, path):
     329                  if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)):
     330                          msg = '[%s:tmp_dir]: invalid path [%s]' % (self.__class__.__name__, path)
     331                          _log.error(msg)
     332                          raise ValueError(msg)
     333                  _log.debug('previous temp dir: %s', tempfile.gettempdir())
     334                  self.__tmp_dir = path
     335                  tempfile.tempdir = self.__tmp_dir
     336                  self.__tmp_dir_already_set = True
     337
     338 -        def _get_tmp_dir(self):
     339                  return self.__tmp_dir
     340
     341          tmp_dir = property(_get_tmp_dir, _set_tmp_dir)
     342  #===========================================================================
     343  # file related tools
     344  #---------------------------------------------------------------------------
     345 -def gpg_decrypt_file(filename=None, passphrase=None):
     346
     347          if platform.system() == 'Windows':
     348                  exec_name = 'gpg.exe'
     349          else:
     350                  exec_name = 'gpg'
     351
     352          tmp, fname = os.path.split(filename)
     353          basename, tmp = os.path.splitext(fname)
     354          filename_decrypted = get_unique_filename(prefix = '%s-decrypted-' % basename)
     355
     356          args = [exec_name, '--verbose', '--batch', '--yes', '--passphrase-fd', '0', '--output', filename_decrypted, '--decrypt', filename]
     357          _log.debug('GnuPG args: %s' % str(args))
     358
     359          try:
     360                  gpg = subprocess.Popen (
     361                          args = args,
     362                          stdin = subprocess.PIPE,
     363                          stdout = subprocess.PIPE,
     364                          stderr = subprocess.PIPE,
     365                          close_fds = False
     366                  )
     367          except (OSError, ValueError, subprocess.CalledProcessError):
     368                  _log.exception('there was a problem executing gpg')
     369                  gmDispatcher.send(signal = u'statustext', msg = _('Error running GnuPG. Cannot decrypt data.'), beep = True)
     370                  return
     371
     372          out, error = gpg.communicate(passphrase)
     373          _log.debug('gpg returned [%s]', gpg.returncode)
     374          if gpg.returncode != 0:
     375                  _log.debug('GnuPG STDOUT:\n%s', out)
     376                  _log.debug('GnuPG STDERR:\n%s', error)
     377                  return None
     378
     379          return filename_decrypted
     380  #---------------------------------------------------------------------------
     381 -def file2md5(filename=None, return_hex=True):
     382          blocksize = 2**10 * 128                 # 128k, since md5 uses 128 byte blocks
     383          _log.debug('md5(%s): <%s> byte blocks', filename, blocksize)
     384
     385          f = open(filename, 'rb')
     386
     387          md5 = hashlib.md5()
     388          while True:
     389                  data = f.read(blocksize)
     390                  if not data:
     391                          break
     392                  md5.update(data)
     393
     394          _log.debug('md5(%s): %s', filename, md5.hexdigest())
     395
     396          if return_hex:
     397                  return md5.hexdigest()
     398          return md5.digest()
     399  #---------------------------------------------------------------------------
     400 -def unicode2charset_encoder(unicode_csv_data, encoding='utf-8'):
     401          for line in unicode_csv_data:
     402                  yield line.encode(encoding)
     403
     404  #def utf_8_encoder(unicode_csv_data):
     405  #       for line in unicode_csv_data:
     406  #               yield line.encode('utf-8')
     407
     408  default_csv_reader_rest_key = u'list_of_values_of_unknown_fields'
     409
     410 -def unicode_csv_reader(unicode_csv_data, dialect=csv.excel, encoding='utf-8', **kwargs):
     411
     412          # csv.py doesn't do Unicode; encode temporarily as UTF-8:
     413          try:
     414                  is_dict_reader = kwargs['dict']
     415                  del kwargs['dict']
     416                  if is_dict_reader is not True:
     417                          raise KeyError
     418                  kwargs['restkey'] = default_csv_reader_rest_key
     419                  csv_reader = csv.DictReader(unicode2charset_encoder(unicode_csv_data), dialect=dialect, **kwargs)
     420          except KeyError:
     421                  is_dict_reader = False
     422                  csv_reader = csv.reader(unicode2charset_encoder(unicode_csv_data), dialect=dialect, **kwargs)
     423
     424          for row in csv_reader:
     425                  # decode ENCODING back to Unicode, cell by cell:
     426                  if is_dict_reader:
     427                          for key in row.keys():
     428                                  if key == default_csv_reader_rest_key:
     429                                          old_data = row[key]
     430                                          new_data = []
     431                                          for val in old_data:
     432                                                  new_data.append(unicode(val, encoding))
     433                                          row[key] = new_data
     434                                          if default_csv_reader_rest_key not in csv_reader.fieldnames:
     435                                                  csv_reader.fieldnames.append(default_csv_reader_rest_key)
     436                                  else:
     437                                          row[key] = unicode(row[key], encoding)
     438                          yield row
     439                  else:
     440                          yield [ unicode(cell, encoding) for cell in row ]
     441                          #yield [unicode(cell, 'utf-8') for cell in row]
     442
     443  #---------------------------------------------------------------------------
     444 -def fname_stem(filename):
     445          return os.path.splitext(os.path.basename(filename))[0]
     446
     447  #---------------------------------------------------------------------------
     448 -def get_unique_filename(prefix=None, suffix=None, tmp_dir=None):
     449          """This introduces a race condition between the file.close() and
     450          actually using the filename.
     451
     452          The file will NOT exist after calling this function.
     453          """
     454          if tmp_dir is not None:
     455                  if (
     456                          not os.access(tmp_dir, os.F_OK)
     457                                  or
     458                          not os.access(tmp_dir, os.X_OK | os.W_OK)
     459                  ):
     460                          _log.warning('cannot find temporary dir [%s], using system default', tmp_dir)
     461                          tmp_dir = None
     462
     463          kwargs = {'dir': tmp_dir}
     464
     465          if prefix is None:
     466                  kwargs['prefix'] = 'gnumed-'
     467          else:
     468                  kwargs['prefix'] = prefix
     469
     470          if suffix in [None, u'']:
     471                  kwargs['suffix'] = '.tmp'
     472          else:
     473                  if not suffix.startswith('.'):
     474                          suffix = '.' + suffix
     475                  kwargs['suffix'] = suffix
     476
     477          f = tempfile.NamedTemporaryFile(**kwargs)
     478          filename = f.name
     479          f.close()
     480
     481          return filename
     482  #===========================================================================
     483 -def import_module_from_directory(module_path=None, module_name=None, always_remove_path=False):
     484          """Import a module from any location."""
     485
     486          _log.debug('CWD: %s', os.getcwd())
     487
     488          remove_path = always_remove_path or False
     489          if module_path not in sys.path:
     490                  _log.info('appending to sys.path: [%s]' % module_path)
     491                  sys.path.append(module_path)
     492                  remove_path = True
     493
     494          _log.debug('will remove import path: %s', remove_path)
     495
     496          if module_name.endswith('.py'):
     497                  module_name = module_name[:-3]
     498
     499          try:
     500                  module = __import__(module_name)
     501          except StandardError:
     502                  _log.exception('cannot __import__() module [%s] from [%s]' % (module_name, module_path))
     503                  while module_path in sys.path:
     504                          sys.path.remove(module_path)
     505                  raise
     506
     507          _log.info('imported module [%s] as [%s]' % (module_name, module))
     508          if remove_path:
     509                  while module_path in sys.path:
     510                          sys.path.remove(module_path)
     511
     512          return module
     513  #===========================================================================
     514  # text related tools
     515  #---------------------------------------------------------------------------
     516  _kB = 1024
     517  _MB = 1024 * _kB
     518  _GB = 1024 * _MB
     519  _TB = 1024 * _GB
     520  _PB = 1024 * _TB
     521  #---------------------------------------------------------------------------
     522 -def size2str(size=0, template=u'%s'):
     523          if size == 1:
     524                  return template % _('1 Byte')
     525          if size < 10 * _kB:
     526                  return template % _('%s Bytes') % size
     527          if size < _MB:
     528                  return template % u'%.1f kB' % (float(size) / _kB)
     529          if size < _GB:
     530                  return template % u'%.1f MB' % (float(size) / _MB)
     531          if size < _TB:
     532                  return template % u'%.1f GB' % (float(size) / _GB)
     533          if size < _PB:
     534                  return template % u'%.1f TB' % (float(size) / _TB)
     535          return template % u'%.1f PB' % (float(size) / _PB)
     536  #---------------------------------------------------------------------------
     537 -def bool2subst(boolean=None, true_return=True, false_return=False, none_return=None):
     538          if boolean is None:
     539                  return none_return
     540          if boolean:
     541                  return true_return
     542          if not boolean:
     543                  return false_return
     544          raise ValueError('bool2subst(): <boolean> arg must be either of True, False, None')
     545  #---------------------------------------------------------------------------
     546 -def bool2str(boolean=None, true_str='True', false_str='False'):
     547          return bool2subst (
     548                  boolean = bool(boolean),
     549                  true_return = true_str,
     550                  false_return = false_str
     551          )
     552  #---------------------------------------------------------------------------
     553 -def none_if(value=None, none_equivalent=None, strip_string=False):
     554          """Modelled after the SQL NULLIF function."""
     555          if value is None:
     556                  return None
     557          if strip_string:
     558                  stripped = value.strip()
     559          else:
     560                  stripped = value
     561          if stripped == none_equivalent:
     562                  return None
     563          return value
     564  #---------------------------------------------------------------------------
     565 -def coalesce(initial=None, instead=None, template_initial=None, template_instead=None, none_equivalents=None, function_initial=None):
     566          """Modelled after the SQL coalesce function.
     567
     568          To be used to simplify constructs like:
     569
     570                  if initial is None (or in none_equivalents):
     571                          real_value = (template_instead % instead) or instead
     572                  else:
     573                          real_value = (template_initial % initial) or initial
     574                  print real_value
     575
     576          @param initial: the value to be tested for <None>
     577          @type initial: any Python type, must have a __str__ method if template_initial is not None
     578          @param instead: the value to be returned if <initial> is None
     579          @type instead: any Python type, must have a __str__ method if template_instead is not None
     580          @param template_initial: if <initial> is returned replace the value into this template, must contain one <%s>
     581          @type template_initial: string or None
     582          @param template_instead: if <instead> is returned replace the value into this template, must contain one <%s>
     583          @type template_instead: string or None
     584
     585          example:
     586                  function_initial = ('strftime', '%Y-%m-%d')
     587
     588          Ideas:
     589                  - list of insteads: initial, [instead, template], [instead, template], [instead, template], template_initial, ...
     590          """
     591          if none_equivalents is None:
     592                  none_equivalents = [None]
     593
     594          if initial in none_equivalents:
     595
     596                  if template_instead is None:
     597                          return instead
     598
     599                  return template_instead % instead
     600
     601          if function_initial is not None:
     602                  funcname, args = function_initial
     603                  func = getattr(initial, funcname)
     604                  initial = func(args)
     605
     606          if template_initial is None:
     607                  return initial
     608
     609          try:
     610                  return template_initial % initial
     611          except TypeError:
     612                  return template_initial
     613  #---------------------------------------------------------------------------
     614 -def __cap_name(match_obj=None):
     615          val = match_obj.group(0).lower()
     616          if val in ['von', 'van', 'de', 'la', 'l', 'der', 'den']:                        # FIXME: this needs to expand, configurable ?
     617                  return val
     618          buf = list(val)
     619          buf[0] = buf[0].upper()
     620          for part in ['mac', 'mc', 'de', 'la']:
     621                  if len(val) > len(part) and val[:len(part)] == part:
     622                          buf[len(part)] = buf[len(part)].upper()
     623          return ''.join(buf)
     624  #---------------------------------------------------------------------------
     625 -def capitalize(text=None, mode=CAPS_NAMES):
     626          """Capitalize the first character but leave the rest alone.
     627
     628          Note that we must be careful about the locale, this may
     629          have issues ! However, for UTF strings it should just work.
     630          """
     631          if (mode is None) or (mode == CAPS_NONE):
     632                  return text
     633
     634          if len(text) == 0:
     635                  return text
     636
     637          if mode == CAPS_FIRST:
     638                  if len(text) == 1:
     639                          return text[0].upper()
     640                  return text[0].upper() + text[1:]
     641
     642          if mode == CAPS_ALLCAPS:
     643                  return text.upper()
     644
     645          if mode == CAPS_FIRST_ONLY:
     646                  if len(text) == 1:
     647                          return text[0].upper()
     648                  return text[0].upper() + text[1:].lower()
     649
     650          if mode == CAPS_WORDS:
     651                  return regex.sub(ur'(\w)(\w+)', lambda x: x.group(1).upper() + x.group(2).lower(), text)
     652
     653          if mode == CAPS_NAMES:
     654                  #return regex.sub(r'\w+', __cap_name, text)
     655                  return capitalize(text=text, mode=CAPS_FIRST)           # until fixed
     656
     657          print "ERROR: invalid capitalization mode: [%s], leaving input as is" % mode
     658          return text
     659  #---------------------------------------------------------------------------
     660 -def input2decimal(initial=None):
     661
     662          if isinstance(initial, decimal.Decimal):
     663                  return True, initial
     664
     665          val = initial
     666
     667          # float ? -> to string first
     668          if type(val) == type(float(1.4)):
     669                  val = str(val)
     670
     671          # string ? -> "," to "."
     672          if isinstance(val, basestring):
     673                  val = val.replace(',', '.', 1)
     674                  val = val.strip()
     675
     676          try:
     677                  d = decimal.Decimal(val)
     678                  return True, d
     679          except (TypeError, decimal.InvalidOperation):
     680                  return False, val
     681  #---------------------------------------------------------------------------
     682 -def input2int(initial=None, minval=None, maxval=None):
     683
     684          val = initial
     685
     686          # string ? -> "," to "."
     687          if isinstance(val, basestring):
     688                  val = val.replace(',', '.', 1)
     689                  val = val.strip()
     690
     691          try:
     692                  int_val = int(val)
     693          except (TypeError, ValueError):
     694                  _log.exception('int(%s) failed', val)
     695                  return False, val
     696
     697          if minval is not None:
     698                  if int_val < minval:
     699                          _log.debug('%s < min (%s)', val, minval)
     700                          return False, val
     701          if maxval is not None:
     702                  if int_val > maxval:
     703                          _log.debug('%s > max (%s)', val, maxval)
     704                          return False, val
     705
     706          return True, int_val
     707  #---------------------------------------------------------------------------
     708 -def strip_leading_empty_lines(lines=None, text=None, eol=u'\n', return_list=True):
     709          if lines is None:
     710                  lines = text.split(eol)
     711
     712          while True:
     713                  if lines[0].strip(eol).strip() != u'':
     714                          break
     715                  lines = lines[1:]
     716
     717          if return_list:
     718                  return lines
     719
     720          return eol.join(lines)
     721  #---------------------------------------------------------------------------
     722 -def strip_trailing_empty_lines(lines=None, text=None, eol=u'\n', return_list=True):
     723          if lines is None:
     724                  lines = text.split(eol)
     725
     726          while True:
     727                  if lines[-1].strip(eol).strip() != u'':
     728                          break
     729                  lines = lines[:-1]
     730
     731          if return_list:
     732                  return lines
     733
     734          return eol.join(lines)
     735  #---------------------------------------------------------------------------
     736 -def strip_empty_lines(lines=None, text=None, eol=u'\n', return_list=True):
     737          return strip_trailing_empty_lines (
     738                  lines = strip_leading_empty_lines(lines = lines, text = text, eol = eol, return_list = True),
     739                  text = None,
     740                  eol = eol,
     741                  return_list = return_list
     742          )
     743  #---------------------------------------------------------------------------
     744 -def wrap(text=None, width=None, initial_indent=u'', subsequent_indent=u'', eol=u'\n'):
     745          """A word-wrap function that preserves existing line breaks
     746          and most spaces in the text. Expects that existing line
     747          breaks are posix newlines (\n).
     748          """
     749          if width is None:
     750                  return text
     751          wrapped = initial_indent + reduce (
     752                  lambda line, word, width=width: '%s%s%s' % (
     753                          line,
     754                          ' \n'[(len(line) - line.rfind('\n') - 1 + len(word.split('\n',1)[0]) >= width)],
     755                          word
     756                  ),
     757                  text.split(' ')
     758          )
     759
     760          if subsequent_indent != u'':
     761                  wrapped = (u'\n%s' % subsequent_indent).join(wrapped.split('\n'))
     762
     763          if eol != u'\n':
     764                  wrapped = wrapped.replace('\n', eol)
     765
     766          return wrapped
     767  #---------------------------------------------------------------------------
     768 -def unwrap(text=None, max_length=None, strip_whitespace=True, remove_empty_lines=True, line_separator = u' // '):
     769
     770          text = text.replace(u'\r', u'')
     771          lines = text.split(u'\n')
     772          text = u''
     773          for line in lines:
     774
     775                  if strip_whitespace:
     776                          line = line.strip().strip(u'\t').strip()
     777
     778                  if remove_empty_lines:
     779                          if line == u'':
     780                                  continue
     781
     782                  text += (u'%s%s' % (line, line_separator))
     783
     784          text = text.rstrip(line_separator)
     785
     786          if max_length is not None:
     787                  text = text[:max_length]
     788
     789          text = text.rstrip(line_separator)
     790
     791          return text
     792  #---------------------------------------------------------------------------
     793 -def xml_escape_string(text=None):
     794          """check for special XML characters and transform them"""
     795          return xml_tools.escape(text)
     796  #---------------------------------------------------------------------------
     797 -def tex_escape_string(text=None, replace_known_unicode=True):
     798          """check for special TeX characters and transform them"""
     799
     800          text = text.replace(u'\\', u'\\textbackslash')
     801          text = text.replace(u'^', u'\\textasciicircum')
     802          text = text.replace(u'~', u'\\textasciitilde')
     803
     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          text = text.replace(u'#', u'\\#')
     809          text = text.replace(u'$', u'\\$')
     810          text = text.replace(u'_', u'\\_')
     811
     812          if replace_known_unicode:
     813                  # this should NOT be replaced for Xe(La)Tex
     814                  text = text.replace(u_euro, u'\\EUR')
     815
     816          return text
     817  #---------------------------------------------------------------------------
     818 -def xetex_escape_string(text=None):
     819          # a web search did not reveal anything else for Xe(La)Tex
     820          # as opposed to LaTeX, except true unicode chars
     821          return tex_escape_string(text = text, replace_known_unicode = False)
     822  #---------------------------------------------------------------------------
     823 -def prompted_input(prompt=None, default=None):
     824          """Obtains entry from standard input.
     825
     826          prompt: Prompt text to display in standard output
     827          default: Default value (for user to press enter only)
     828          CTRL-C: aborts and returns None
     829          """
     830          if prompt is None:
     831                  msg = u'(CTRL-C aborts)'
     832          else:
     833                  msg = u'%s (CTRL-C aborts)' % prompt
     834
     835          if default is None:
     836                  msg = msg + u': '
     837          else:
     838                  msg = u'%s [%s]: ' % (msg, default)
     839
     840          try:
     841                  usr_input = raw_input(msg)
     842          except KeyboardInterrupt:
     843                  return None
     844
     845          if usr_input == '':
     846                  return default
     847
     848          return usr_input
     849
     850  #===========================================================================
     851  # image handling tools
     852  #---------------------------------------------------------------------------
     853  # builtin (ugly but tried and true) fallback icon
     854  __icon_serpent = \
     855  """x\xdae\x8f\xb1\x0e\x83 \x10\x86w\x9f\xe2\x92\x1blb\xf2\x07\x96\xeaH:0\xd6\
     856  \xc1\x85\xd5\x98N5\xa5\xef?\xf5N\xd0\x8a\xdcA\xc2\xf7qw\x84\xdb\xfa\xb5\xcd\
     857  \xd4\xda;\xc9\x1a\xc8\xb6\xcd<\xb5\xa0\x85\x1e\xeb\xbc\xbc7b!\xf6\xdeHl\x1c\
     858  \x94\x073\xec<*\xf7\xbe\xf7\x99\x9d\xb21~\xe7.\xf5\x1f\x1c\xd3\xbdVlL\xc2\
     859  \xcf\xf8ye\xd0\x00\x90\x0etH \x84\x80B\xaa\x8a\x88\x85\xc4(U\x9d$\xfeR;\xc5J\
     860  \xa6\x01\xbbt9\xceR\xc8\x81e_$\x98\xb9\x9c\xa9\x8d,y\xa9t\xc8\xcf\x152\xe0x\
     861  \xe9$\xf5\x07\x95\x0cD\x95t:\xb1\x92\xae\x9cI\xa8~\x84\x1f\xe0\xa3ec"""
     862
     863 -def get_icon(wx=None):
     864
     865          paths = gmPaths(app_name = u'gnumed', wx = wx)
     866
     867          candidates = [
     868                  os.path.join(paths.system_app_data_dir, 'bitmaps', 'gm_icon-serpent_and_gnu.png'),
     869                  os.path.join(paths.local_base_dir, 'bitmaps', 'gm_icon-serpent_and_gnu.png'),
     870                  os.path.join(paths.system_app_data_dir, 'bitmaps', 'serpent.png'),
     871                  os.path.join(paths.local_base_dir, 'bitmaps', 'serpent.png')
     872          ]
     873
     874          found_as = None
     875          for candidate in candidates:
     876                  try:
     877                          open(candidate, 'r').close()
     878                          found_as = candidate
     879                          break
     880                  except IOError:
     881                          _log.debug('icon not found in [%s]', candidate)
     882
     883          if found_as is None:
     884                  _log.warning('no icon file found, falling back to builtin (ugly) icon')
     885                  icon_bmp_data = wx.BitmapFromXPMData(cPickle.loads(zlib.decompress(__icon_serpent)))
     886                  icon.CopyFromBitmap(icon_bmp_data)
     887          else:
     888                  _log.debug('icon found in [%s]', found_as)
     889                  icon = wx.EmptyIcon()
     890                  try:
     891                          icon.LoadFile(found_as, wx.BITMAP_TYPE_ANY)             #_PNG
     892                  except AttributeError:
     893                          _log.exception(u"this platform doesn't support wx.Icon().LoadFile()")
     894
     895          return icon
     896  #===========================================================================
     897  # main
     898  #---------------------------------------------------------------------------
     899  if __name__ == '__main__':
     900
     901          if len(sys.argv) < 2:
     902                  sys.exit()
     903
     904          if sys.argv[1] != 'test':
     905                  sys.exit()
     906
     907          #-----------------------------------------------------------------------
     908 -        def test_input2decimal():
     909
     910                  tests = [
     911                          [None, False],
     912
     913                          ['', False],
     914                          [' 0 ', True, 0],
     915
     916                          [0, True, 0],
     917                          [0.0, True, 0],
     918                          [.0, True, 0],
     919                          ['0', True, 0],
     920                          ['0.0', True, 0],
     921                          ['0,0', True, 0],
     922                          ['00.0', True, 0],
     923                          ['.0', True, 0],
     924                          [',0', True, 0],
     925
     926                          [0.1, True, decimal.Decimal('0.1')],
     927                          [.01, True, decimal.Decimal('0.01')],
     928                          ['0.1', True, decimal.Decimal('0.1')],
     929                          ['0,1', True, decimal.Decimal('0.1')],
     930                          ['00.1', True, decimal.Decimal('0.1')],
     931                          ['.1', True, decimal.Decimal('0.1')],
     932                          [',1', True, decimal.Decimal('0.1')],
     933
     934                          [1, True, 1],
     935                          [1.0, True, 1],
     936                          ['1', True, 1],
     937                          ['1.', True, 1],
     938                          ['1,', True, 1],
     939                          ['1.0', True, 1],
     940                          ['1,0', True, 1],
     941                          ['01.0', True, 1],
     942                          ['01,0', True, 1],
     943                          [' 01, ', True, 1],
     944
     945                          [decimal.Decimal('1.1'), True, decimal.Decimal('1.1')]
     946                  ]
     947                  for test in tests:
     948                          conversion_worked, result = input2decimal(initial = test[0])
     949
     950                          expected2work = test[1]
     951
     952                          if conversion_worked:
     953                                  if expected2work:
     954                                          if result == test[2]:
     955                                                  continue
     956                                          else:
     957                                                  print "ERROR (conversion result wrong): >%s<, expected >%s<, got >%s<" % (test[0], test[2], result)
     958                                  else:
     959                                          print "ERROR (conversion worked but was expected to fail): >%s<, got >%s<" % (test[0], result)
     960                          else:
     961                                  if not expected2work:
     962                                          continue
     963                                  else:
     964                                          print "ERROR (conversion failed but was expected to work): >%s<, expected >%s<" % (test[0], test[2])
     965          #-----------------------------------------------------------------------
     966 -        def test_input2int():
     967                  print input2int(0)
     968                  print input2int('0')
     969                  print input2int(u'0', 0, 0)
     970          #-----------------------------------------------------------------------
     971 -        def test_coalesce():
     972
     973                  import datetime as dt
     974                  print coalesce(initial = dt.datetime.now(), template_initial = u'-- %s --', function_initial = ('strftime', u'%Y-%m-%d'))
     975
     976                  print 'testing coalesce()'
     977                  print "------------------"
     978                  tests = [
     979                          [None, 'something other than <None>', None, None, 'something other than <None>'],
     980                          ['Captain', 'Mr.', '%s.'[:4], 'Mr.', 'Capt.'],
     981                          ['value to test', 'test 3 failed', 'template with "%s" included', None, 'template with "value to test" included'],
     982                          ['value to test', 'test 4 failed', 'template with value not included', None, 'template with value not included'],
     983                          [None, 'initial value was None', 'template_initial: %s', None, 'initial value was None'],
     984                          [None, 'initial value was None', 'template_initial: %%(abc)s', None, 'initial value was None']
     985                  ]
     986                  passed = True
     987                  for test in tests:
     988                          result = coalesce (
     989                                  initial = test[0],
     990                                  instead = test[1],
     991                                  template_initial = test[2],
     992                                  template_instead = test[3]
     993                          )
     994                          if result != test[4]:
     995                                  print "ERROR"
     996                                  print "coalesce: (%s, %s, %s, %s)" % (test[0], test[1], test[2], test[3])
     997                                  print "expected:", test[4]
     998                                  print "received:", result
     999                                  passed = False
    1000
    1001                  if passed:
    1002                          print "passed"
    1003                  else:
    1004                          print "failed"
    1005                  return passed
    1006          #-----------------------------------------------------------------------
    1007 -        def test_capitalize():
    1008                  print 'testing capitalize() ...'
    1009                  success = True
    1010                  pairs = [
    1011                          # [original, expected result, CAPS mode]
    1012                          [u'Boot', u'Boot', CAPS_FIRST_ONLY],
    1013                          [u'boot', u'Boot', CAPS_FIRST_ONLY],
    1014                          [u'booT', u'Boot', CAPS_FIRST_ONLY],
    1015                          [u'BoOt', u'Boot', CAPS_FIRST_ONLY],
    1016                          [u'boots-Schau', u'Boots-Schau', CAPS_WORDS],
    1017                          [u'boots-sChau', u'Boots-Schau', CAPS_WORDS],
    1018                          [u'boot camp', u'Boot Camp', CAPS_WORDS],
    1019                          [u'fahrner-Kampe', u'Fahrner-Kampe', CAPS_NAMES],
    1020                          [u'häkkönen', u'Häkkönen', CAPS_NAMES],
    1021                          [u'McBurney', u'McBurney', CAPS_NAMES],
    1022                          [u'mcBurney', u'McBurney', CAPS_NAMES],
    1023                          [u'blumberg', u'Blumberg', CAPS_NAMES],
    1024                          [u'roVsing', u'RoVsing', CAPS_NAMES],
    1025                          [u'Özdemir', u'Özdemir', CAPS_NAMES],
    1026                          [u'özdemir', u'Özdemir', CAPS_NAMES],
    1027                  ]
    1028                  for pair in pairs:
    1029                          result = capitalize(pair[0], pair[2])
    1030                          if result != pair[1]:
    1031                                  success = False
    1032                                  print 'ERROR (caps mode %s): "%s" -> "%s", expected "%s"' % (pair[2], pair[0], result, pair[1])
    1033
    1034                  if success:
    1035                          print "... SUCCESS"
    1036
    1037                  return success
    1038          #-----------------------------------------------------------------------
    1039 -        def test_import_module():
    1040                  print "testing import_module_from_directory()"
    1041                  path = sys.argv[1]
    1042                  name = sys.argv[2]
    1043                  try:
    1044                          mod = import_module_from_directory(module_path = path, module_name = name)
    1045                  except:
    1046                          print "module import failed, see log"
    1047                          return False
    1048
    1049                  print "module import succeeded", mod
    1050                  print dir(mod)
    1051                  return True
    1052          #-----------------------------------------------------------------------
    1053 -        def test_mkdir():
    1054                  print "testing mkdir()"
    1055                  mkdir(sys.argv[1])
    1056          #-----------------------------------------------------------------------
    1057 -        def test_gmPaths():
    1058                  print "testing gmPaths()"
    1059                  print "-----------------"
    1060                  paths = gmPaths(wx=None, app_name='gnumed')
    1061                  print "user     config dir:", paths.user_config_dir
    1062                  print "system   config dir:", paths.system_config_dir
    1063                  print "local      base dir:", paths.local_base_dir
    1064                  print "system app data dir:", paths.system_app_data_dir
    1065                  print "working directory  :", paths.working_dir
    1066                  print "temp directory     :", paths.tmp_dir
    1067          #-----------------------------------------------------------------------
    1068 -        def test_none_if():
    1069                  print "testing none_if()"
    1070                  print "-----------------"
    1071                  tests = [
    1072                          [None, None, None],
    1073                          ['a', 'a', None],
    1074                          ['a', 'b', 'a'],
    1075                          ['a', None, 'a'],
    1076                          [None, 'a', None],
    1077                          [1, 1, None],
    1078                          [1, 2, 1],
    1079                          [1, None, 1],
    1080                          [None, 1, None]
    1081                  ]
    1082
    1083                  for test in tests:
    1084                          if none_if(value = test[0], none_equivalent = test[1]) != test[2]:
    1085                                  print 'ERROR: none_if(%s) returned [%s], expected [%s]' % (test[0], none_if(test[0], test[1]), test[2])
    1086
    1087                  return True
    1088          #-----------------------------------------------------------------------
    1089 -        def test_bool2str():
    1090                  tests = [
    1091                          [True, 'Yes', 'Yes', 'Yes'],
    1092                          [False, 'OK', 'not OK', 'not OK']
    1093                  ]
    1094                  for test in tests:
    1095                          if bool2str(test[0], test[1], test[2]) != test[3]:
    1096                                  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])
    1097
    1098                  return True
    1099          #-----------------------------------------------------------------------
    1100 -        def test_bool2subst():
    1101
    1102                  print bool2subst(True, 'True', 'False', 'is None')
    1103                  print bool2subst(False, 'True', 'False', 'is None')
    1104                  print bool2subst(None, 'True', 'False', 'is None')
    1105          #-----------------------------------------------------------------------
    1106 -        def test_get_unique_filename():
    1107                  print get_unique_filename()
    1108                  print get_unique_filename(prefix='test-')
    1109                  print get_unique_filename(suffix='tst')
    1110                  print get_unique_filename(prefix='test-', suffix='tst')
    1111                  print get_unique_filename(tmp_dir='/home/ncq/Archiv/')
    1112          #-----------------------------------------------------------------------
    1113 -        def test_size2str():
    1114                  print "testing size2str()"
    1115                  print "------------------"
    1116                  tests = [0, 1, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000, 1000000000000, 10000000000000]
    1117                  for test in tests:
    1118                          print size2str(test)
    1119          #-----------------------------------------------------------------------
    1120 -        def test_unwrap():
    1121
    1122                  test = """
    1123  second line\n
    1124          3rd starts with tab  \n
    1125   4th with a space       \n
    1126
    1127  6th
    1128
    1129  """
    1130                  print unwrap(text = test, max_length = 25)
    1131          #-----------------------------------------------------------------------
    1132 -        def test_wrap():
    1133                  test = 'line 1\nline 2\nline 3'
    1134
    1135                  print "wrap 5-6-7 initial 0, subsequent 0"
    1136                  print wrap(test, 5)
    1137                  print
    1138                  print wrap(test, 6)
    1139                  print
    1140                  print wrap(test, 7)
    1141                  print "-------"
    1142                  raw_input()
    1143                  print "wrap 5 initial 1-1-3, subsequent 1-3-1"
    1144                  print wrap(test, 5, u' ', u' ')
    1145                  print
    1146                  print wrap(test, 5, u' ', u'   ')
    1147                  print
    1148                  print wrap(test, 5, u'   ', u' ')
    1149                  print "-------"
    1150                  raw_input()
    1151                  print "wrap 6 initial 1-1-3, subsequent 1-3-1"
    1152                  print wrap(test, 6, u' ', u' ')
    1153                  print
    1154                  print wrap(test, 6, u' ', u'   ')
    1155                  print
    1156                  print wrap(test, 6, u'   ', u' ')
    1157                  print "-------"
    1158                  raw_input()
    1159                  print "wrap 7 initial 1-1-3, subsequent 1-3-1"
    1160                  print wrap(test, 7, u' ', u' ')
    1161                  print
    1162                  print wrap(test, 7, u' ', u'   ')
    1163                  print
    1164                  print wrap(test, 7, u'   ', u' ')
    1165          #-----------------------------------------------------------------------
    1166 -        def test_md5():
    1167                  print '%s: %s' % (sys.argv[2], file2md5(sys.argv[2]))
    1168          #-----------------------------------------------------------------------
    1169 -        def test_unicode():
    1170                  print u_link_symbol * 10
    1171          #-----------------------------------------------------------------------
    1172 -        def test_xml_escape():
    1173                  print xml_escape_string(u'<')
    1174                  print xml_escape_string(u'>')
    1175                  print xml_escape_string(u'&')
    1176          #-----------------------------------------------------------------------
    1177 -        def test_tex_escape():
    1178                  tests = [u'\\', u'^', u'~', u'{', u'}', u'%',  u'&', u'#', u'$', u'_', u_euro]
    1179                  tests.append(u'  '.join(tests))
    1180                  for test in tests:
    1181                          print u'%s:' % test, tex_escape_string(test)
    1182          #-----------------------------------------------------------------------
    1183 -        def test_gpg_decrypt():
    1184                  fname = gpg_decrypt_file(filename = sys.argv[2], passphrase = sys.argv[3])
    1185                  if fname is not None:
    1186                          print "successfully decrypted:", fname
    1187          #-----------------------------------------------------------------------
    1188 -        def test_strip_trailing_empty_lines():
    1189                  tests = [
    1190                          u'one line, no embedded line breaks  ',
    1191                          u'one line\nwith embedded\nline\nbreaks\n   '
    1192                  ]
    1193                  for test in tests:
    1194                          print 'as list:'
    1195                          print strip_trailing_empty_lines(text = test, eol=u'\n', return_list = True)
    1196                          print 'as string:'
    1197                          print u'>>>%s<<<' % strip_trailing_empty_lines(text = test, eol=u'\n', return_list = False)
    1198                  tests = [
    1199                          ['list', 'without', 'empty', 'trailing', 'lines'],
    1200                          ['list', 'with', 'empty', 'trailing', 'lines', '', '  ', '']
    1201                  ]
    1202                  for test in tests:
    1203                          print 'as list:'
    1204                          print strip_trailing_empty_lines(lines = test, eol = u'\n', return_list = True)
    1205                          print 'as string:'
    1206                          print strip_trailing_empty_lines(lines = test, eol = u'\n', return_list = False)
    1207          #-----------------------------------------------------------------------
    1208 -        def test_fname_stem():
    1209                  tests = [
    1210                          r'abc.exe',
    1211                          r'\abc.exe',
    1212                          r'c:\abc.exe',
    1213                          r'c:\d\abc.exe',
    1214                          r'/home/ncq/tmp.txt',
    1215                          r'~/tmp.txt',
    1216                          r'./tmp.txt',
    1217                          r'./.././tmp.txt',
    1218                          r'tmp.txt'
    1219                  ]
    1220                  for t in tests:
    1221                          print "[%s] -> [%s]" % (t, fname_stem(t))
    1222          #-----------------------------------------------------------------------
    1223          #test_coalesce()
    1224          #test_capitalize()
    1225          #test_import_module()
    1226          #test_mkdir()
    1227          #test_gmPaths()
    1228          #test_none_if()
    1229          #test_bool2str()
    1230          #test_bool2subst()
    1231          #test_get_unique_filename()
    1232          #test_size2str()
    1233          #test_wrap()
    1234          #test_input2decimal()
    1235          #test_input2int()
    1236          #test_unwrap()
    1237          #test_md5()
    1238          #test_unicode()
    1239          #test_xml_escape()
    1240          #test_gpg_decrypt()
    1241          #test_strip_trailing_empty_lines()
    1242          test_fname_stem()
    1243          #test_tex_escape()
    1244
    1245  #===========================================================================
    1246