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'
      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 wrap(text=None, width=None, initial_indent=u'', subsequent_indent=u'', eol=u'\n'):
     737          """A word-wrap function that preserves existing line breaks
     738          and most spaces in the text. Expects that existing line
     739          breaks are posix newlines (\n).
     740          """
     741          if width is None:
     742                  return text
     743          wrapped = initial_indent + reduce (
     744                  lambda line, word, width=width: '%s%s%s' % (
     745                          line,
     746                          ' \n'[(len(line) - line.rfind('\n') - 1 + len(word.split('\n',1)[0]) >= width)],
     747                          word
     748                  ),
     749                  text.split(' ')
     750          )
     751
     752          if subsequent_indent != u'':
     753                  wrapped = (u'\n%s' % subsequent_indent).join(wrapped.split('\n'))
     754
     755          if eol != u'\n':
     756                  wrapped = wrapped.replace('\n', eol)
     757
     758          return wrapped
     759  #---------------------------------------------------------------------------
     760 -def unwrap(text=None, max_length=None, strip_whitespace=True, remove_empty_lines=True, line_separator = u' // '):
     761
     762          text = text.replace(u'\r', u'')
     763          lines = text.split(u'\n')
     764          text = u''
     765          for line in lines:
     766
     767                  if strip_whitespace:
     768                          line = line.strip().strip(u'\t').strip()
     769
     770                  if remove_empty_lines:
     771                          if line == u'':
     772                                  continue
     773
     774                  text += (u'%s%s' % (line, line_separator))
     775
     776          text = text.rstrip(line_separator)
     777
     778          if max_length is not None:
     779                  text = text[:max_length]
     780
     781          text = text.rstrip(line_separator)
     782
     783          return text
     784  #---------------------------------------------------------------------------
     785 -def xml_escape_string(text=None):
     786          """check for special XML characters and transform them"""
     787          return xml_tools.escape(text)
     788  #---------------------------------------------------------------------------
     789 -def tex_escape_string(text=None, replace_known_unicode=True):
     790          """check for special TeX characters and transform them"""
     791
     792          text = text.replace(u'\\', u'\\textbackslash')
     793          text = text.replace(u'^', u'\\textasciicircum')
     794          text = text.replace(u'~', u'\\textasciitilde')
     795
     796          text = text.replace(u'{', u'\\{')
     797          text = text.replace(u'}', u'\\}')
     798          text = text.replace(u'%', u'\\%')
     799          text = text.replace(u'&', u'\\&')
     800          text = text.replace(u'#', u'\\#')
     801          text = text.replace(u'$', u'\\$')
     802          text = text.replace(u'_', u'\\_')
     803
     804          if replace_known_unicode:
     805                  # this should NOT be replaced for Xe(La)Tex
     806                  text = text.replace(u_euro, u'\\EUR')
     807
     808          return text
     809  #---------------------------------------------------------------------------
     810 -def xetex_escape_string(text=None):
     811          # a web search did not reveal anything else for Xe(La)Tex
     812          # as opposed to LaTeX, except true unicode chars
     813          return tex_escape_string(text = text, replace_known_unicode = False)
     814  #---------------------------------------------------------------------------
     815 -def prompted_input(prompt=None, default=None):
     816          """Obtains entry from standard input.
     817
     818          prompt: Prompt text to display in standard output
     819          default: Default value (for user to press enter only)
     820          CTRL-C: aborts and returns None
     821          """
     822          if prompt is None:
     823                  msg = u'(CTRL-C aborts)'
     824          else:
     825                  msg = u'%s (CTRL-C aborts)' % prompt
     826
     827          if default is None:
     828                  msg = msg + u': '
     829          else:
     830                  msg = u'%s [%s]: ' % (msg, default)
     831
     832          try:
     833                  usr_input = raw_input(msg)
     834          except KeyboardInterrupt:
     835                  return None
     836
     837          if usr_input == '':
     838                  return default
     839
     840          return usr_input
     841
     842  #===========================================================================
     843  # image handling tools
     844  #---------------------------------------------------------------------------
     845  # builtin (ugly but tried and true) fallback icon
     846  __icon_serpent = \
     847  """x\xdae\x8f\xb1\x0e\x83 \x10\x86w\x9f\xe2\x92\x1blb\xf2\x07\x96\xeaH:0\xd6\
     848  \xc1\x85\xd5\x98N5\xa5\xef?\xf5N\xd0\x8a\xdcA\xc2\xf7qw\x84\xdb\xfa\xb5\xcd\
     849  \xd4\xda;\xc9\x1a\xc8\xb6\xcd<\xb5\xa0\x85\x1e\xeb\xbc\xbc7b!\xf6\xdeHl\x1c\
     850  \x94\x073\xec<*\xf7\xbe\xf7\x99\x9d\xb21~\xe7.\xf5\x1f\x1c\xd3\xbdVlL\xc2\
     851  \xcf\xf8ye\xd0\x00\x90\x0etH \x84\x80B\xaa\x8a\x88\x85\xc4(U\x9d$\xfeR;\xc5J\
     852  \xa6\x01\xbbt9\xceR\xc8\x81e_$\x98\xb9\x9c\xa9\x8d,y\xa9t\xc8\xcf\x152\xe0x\
     853  \xe9$\xf5\x07\x95\x0cD\x95t:\xb1\x92\xae\x9cI\xa8~\x84\x1f\xe0\xa3ec"""
     854
     855 -def get_icon(wx=None):
     856
     857          paths = gmPaths(app_name = u'gnumed', wx = wx)
     858
     859          candidates = [
     860                  os.path.join(paths.system_app_data_dir, 'bitmaps', 'gm_icon-serpent_and_gnu.png'),
     861                  os.path.join(paths.local_base_dir, 'bitmaps', 'gm_icon-serpent_and_gnu.png'),
     862                  os.path.join(paths.system_app_data_dir, 'bitmaps', 'serpent.png'),
     863                  os.path.join(paths.local_base_dir, 'bitmaps', 'serpent.png')
     864          ]
     865
     866          found_as = None
     867          for candidate in candidates:
     868                  try:
     869                          open(candidate, 'r').close()
     870                          found_as = candidate
     871                          break
     872                  except IOError:
     873                          _log.debug('icon not found in [%s]', candidate)
     874
     875          if found_as is None:
     876                  _log.warning('no icon file found, falling back to builtin (ugly) icon')
     877                  icon_bmp_data = wx.BitmapFromXPMData(cPickle.loads(zlib.decompress(__icon_serpent)))
     878                  icon.CopyFromBitmap(icon_bmp_data)
     879          else:
     880                  _log.debug('icon found in [%s]', found_as)
     881                  icon = wx.EmptyIcon()
     882                  try:
     883                          icon.LoadFile(found_as, wx.BITMAP_TYPE_ANY)             #_PNG
     884                  except AttributeError:
     885                          _log.exception(u"this platform doesn't support wx.Icon().LoadFile()")
     886
     887          return icon
     888  #===========================================================================
     889  # main
     890  #---------------------------------------------------------------------------
     891  if __name__ == '__main__':
     892
     893          if len(sys.argv) < 2:
     894                  sys.exit()
     895
     896          if sys.argv[1] != 'test':
     897                  sys.exit()
     898
     899          #-----------------------------------------------------------------------
     900 -        def test_input2decimal():
     901
     902                  tests = [
     903                          [None, False],
     904
     905                          ['', False],
     906                          [' 0 ', True, 0],
     907
     908                          [0, True, 0],
     909                          [0.0, True, 0],
     910                          [.0, True, 0],
     911                          ['0', True, 0],
     912                          ['0.0', True, 0],
     913                          ['0,0', True, 0],
     914                          ['00.0', True, 0],
     915                          ['.0', True, 0],
     916                          [',0', True, 0],
     917
     918                          [0.1, True, decimal.Decimal('0.1')],
     919                          [.01, True, decimal.Decimal('0.01')],
     920                          ['0.1', True, decimal.Decimal('0.1')],
     921                          ['0,1', True, decimal.Decimal('0.1')],
     922                          ['00.1', True, decimal.Decimal('0.1')],
     923                          ['.1', True, decimal.Decimal('0.1')],
     924                          [',1', True, decimal.Decimal('0.1')],
     925
     926                          [1, True, 1],
     927                          [1.0, True, 1],
     928                          ['1', True, 1],
     929                          ['1.', True, 1],
     930                          ['1,', True, 1],
     931                          ['1.0', True, 1],
     932                          ['1,0', True, 1],
     933                          ['01.0', True, 1],
     934                          ['01,0', True, 1],
     935                          [' 01, ', True, 1],
     936
     937                          [decimal.Decimal('1.1'), True, decimal.Decimal('1.1')]
     938                  ]
     939                  for test in tests:
     940                          conversion_worked, result = input2decimal(initial = test[0])
     941
     942                          expected2work = test[1]
     943
     944                          if conversion_worked:
     945                                  if expected2work:
     946                                          if result == test[2]:
     947                                                  continue
     948                                          else:
     949                                                  print "ERROR (conversion result wrong): >%s<, expected >%s<, got >%s<" % (test[0], test[2], result)
     950                                  else:
     951                                          print "ERROR (conversion worked but was expected to fail): >%s<, got >%s<" % (test[0], result)
     952                          else:
     953                                  if not expected2work:
     954                                          continue
     955                                  else:
     956                                          print "ERROR (conversion failed but was expected to work): >%s<, expected >%s<" % (test[0], test[2])
     957          #-----------------------------------------------------------------------
     958 -        def test_input2int():
     959                  print input2int(0)
     960                  print input2int('0')
     961                  print input2int(u'0', 0, 0)
     962          #-----------------------------------------------------------------------
     963 -        def test_coalesce():
     964
     965                  import datetime as dt
     966                  print coalesce(initial = dt.datetime.now(), template_initial = u'-- %s --', function_initial = ('strftime', u'%Y-%m-%d'))
     967
     968                  print 'testing coalesce()'
     969                  print "------------------"
     970                  tests = [
     971                          [None, 'something other than <None>', None, None, 'something other than <None>'],
     972                          ['Captain', 'Mr.', '%s.'[:4], 'Mr.', 'Capt.'],
     973                          ['value to test', 'test 3 failed', 'template with "%s" included', None, 'template with "value to test" included'],
     974                          ['value to test', 'test 4 failed', 'template with value not included', None, 'template with value not included'],
     975                          [None, 'initial value was None', 'template_initial: %s', None, 'initial value was None'],
     976                          [None, 'initial value was None', 'template_initial: %%(abc)s', None, 'initial value was None']
     977                  ]
     978                  passed = True
     979                  for test in tests:
     980                          result = coalesce (
     981                                  initial = test[0],
     982                                  instead = test[1],
     983                                  template_initial = test[2],
     984                                  template_instead = test[3]
     985                          )
     986                          if result != test[4]:
     987                                  print "ERROR"
     988                                  print "coalesce: (%s, %s, %s, %s)" % (test[0], test[1], test[2], test[3])
     989                                  print "expected:", test[4]
     990                                  print "received:", result
     991                                  passed = False
     992
     993                  if passed:
     994                          print "passed"
     995                  else:
     996                          print "failed"
     997                  return passed
     998          #-----------------------------------------------------------------------
     999 -        def test_capitalize():
    1000                  print 'testing capitalize() ...'
    1001                  success = True
    1002                  pairs = [
    1003                          # [original, expected result, CAPS mode]
    1004                          [u'Boot', u'Boot', CAPS_FIRST_ONLY],
    1005                          [u'boot', u'Boot', CAPS_FIRST_ONLY],
    1006                          [u'booT', u'Boot', CAPS_FIRST_ONLY],
    1007                          [u'BoOt', u'Boot', CAPS_FIRST_ONLY],
    1008                          [u'boots-Schau', u'Boots-Schau', CAPS_WORDS],
    1009                          [u'boots-sChau', u'Boots-Schau', CAPS_WORDS],
    1010                          [u'boot camp', u'Boot Camp', CAPS_WORDS],
    1011                          [u'fahrner-Kampe', u'Fahrner-Kampe', CAPS_NAMES],
    1012                          [u'häkkönen', u'Häkkönen', CAPS_NAMES],
    1013                          [u'McBurney', u'McBurney', CAPS_NAMES],
    1014                          [u'mcBurney', u'McBurney', CAPS_NAMES],
    1015                          [u'blumberg', u'Blumberg', CAPS_NAMES],
    1016                          [u'roVsing', u'RoVsing', CAPS_NAMES],
    1017                          [u'Özdemir', u'Özdemir', CAPS_NAMES],
    1018                          [u'özdemir', u'Özdemir', CAPS_NAMES],
    1019                  ]
    1020                  for pair in pairs:
    1021                          result = capitalize(pair[0], pair[2])
    1022                          if result != pair[1]:
    1023                                  success = False
    1024                                  print 'ERROR (caps mode %s): "%s" -> "%s", expected "%s"' % (pair[2], pair[0], result, pair[1])
    1025
    1026                  if success:
    1027                          print "... SUCCESS"
    1028
    1029                  return success
    1030          #-----------------------------------------------------------------------
    1031 -        def test_import_module():
    1032                  print "testing import_module_from_directory()"
    1033                  path = sys.argv[1]
    1034                  name = sys.argv[2]
    1035                  try:
    1036                          mod = import_module_from_directory(module_path = path, module_name = name)
    1037                  except:
    1038                          print "module import failed, see log"
    1039                          return False
    1040
    1041                  print "module import succeeded", mod
    1042                  print dir(mod)
    1043                  return True
    1044          #-----------------------------------------------------------------------
    1045 -        def test_mkdir():
    1046                  print "testing mkdir()"
    1047                  mkdir(sys.argv[1])
    1048          #-----------------------------------------------------------------------
    1049 -        def test_gmPaths():
    1050                  print "testing gmPaths()"
    1051                  print "-----------------"
    1052                  paths = gmPaths(wx=None, app_name='gnumed')
    1053                  print "user     config dir:", paths.user_config_dir
    1054                  print "system   config dir:", paths.system_config_dir
    1055                  print "local      base dir:", paths.local_base_dir
    1056                  print "system app data dir:", paths.system_app_data_dir
    1057                  print "working directory  :", paths.working_dir
    1058                  print "temp directory     :", paths.tmp_dir
    1059          #-----------------------------------------------------------------------
    1060 -        def test_none_if():
    1061                  print "testing none_if()"
    1062                  print "-----------------"
    1063                  tests = [
    1064                          [None, None, None],
    1065                          ['a', 'a', None],
    1066                          ['a', 'b', 'a'],
    1067                          ['a', None, 'a'],
    1068                          [None, 'a', None],
    1069                          [1, 1, None],
    1070                          [1, 2, 1],
    1071                          [1, None, 1],
    1072                          [None, 1, None]
    1073                  ]
    1074
    1075                  for test in tests:
    1076                          if none_if(value = test[0], none_equivalent = test[1]) != test[2]:
    1077                                  print 'ERROR: none_if(%s) returned [%s], expected [%s]' % (test[0], none_if(test[0], test[1]), test[2])
    1078
    1079                  return True
    1080          #-----------------------------------------------------------------------
    1081 -        def test_bool2str():
    1082                  tests = [
    1083                          [True, 'Yes', 'Yes', 'Yes'],
    1084                          [False, 'OK', 'not OK', 'not OK']
    1085                  ]
    1086                  for test in tests:
    1087                          if bool2str(test[0], test[1], test[2]) != test[3]:
    1088                                  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])
    1089
    1090                  return True
    1091          #-----------------------------------------------------------------------
    1092 -        def test_bool2subst():
    1093
    1094                  print bool2subst(True, 'True', 'False', 'is None')
    1095                  print bool2subst(False, 'True', 'False', 'is None')
    1096                  print bool2subst(None, 'True', 'False', 'is None')
    1097          #-----------------------------------------------------------------------
    1098 -        def test_get_unique_filename():
    1099                  print get_unique_filename()
    1100                  print get_unique_filename(prefix='test-')
    1101                  print get_unique_filename(suffix='tst')
    1102                  print get_unique_filename(prefix='test-', suffix='tst')
    1103                  print get_unique_filename(tmp_dir='/home/ncq/Archiv/')
    1104          #-----------------------------------------------------------------------
    1105 -        def test_size2str():
    1106                  print "testing size2str()"
    1107                  print "------------------"
    1108                  tests = [0, 1, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000, 1000000000000, 10000000000000]
    1109                  for test in tests:
    1110                          print size2str(test)
    1111          #-----------------------------------------------------------------------
    1112 -        def test_unwrap():
    1113
    1114                  test = """
    1115  second line\n
    1116          3rd starts with tab  \n
    1117   4th with a space       \n
    1118
    1119  6th
    1120
    1121  """
    1122                  print unwrap(text = test, max_length = 25)
    1123          #-----------------------------------------------------------------------
    1124 -        def test_wrap():
    1125                  test = 'line 1\nline 2\nline 3'
    1126
    1127                  print "wrap 5-6-7 initial 0, subsequent 0"
    1128                  print wrap(test, 5)
    1129                  print
    1130                  print wrap(test, 6)
    1131                  print
    1132                  print wrap(test, 7)
    1133                  print "-------"
    1134                  raw_input()
    1135                  print "wrap 5 initial 1-1-3, subsequent 1-3-1"
    1136                  print wrap(test, 5, u' ', u' ')
    1137                  print
    1138                  print wrap(test, 5, u' ', u'   ')
    1139                  print
    1140                  print wrap(test, 5, u'   ', u' ')
    1141                  print "-------"
    1142                  raw_input()
    1143                  print "wrap 6 initial 1-1-3, subsequent 1-3-1"
    1144                  print wrap(test, 6, u' ', u' ')
    1145                  print
    1146                  print wrap(test, 6, u' ', u'   ')
    1147                  print
    1148                  print wrap(test, 6, u'   ', u' ')
    1149                  print "-------"
    1150                  raw_input()
    1151                  print "wrap 7 initial 1-1-3, subsequent 1-3-1"
    1152                  print wrap(test, 7, u' ', u' ')
    1153                  print
    1154                  print wrap(test, 7, u' ', u'   ')
    1155                  print
    1156                  print wrap(test, 7, u'   ', u' ')
    1157          #-----------------------------------------------------------------------
    1158 -        def test_md5():
    1159                  print '%s: %s' % (sys.argv[2], file2md5(sys.argv[2]))
    1160          #-----------------------------------------------------------------------
    1161 -        def test_unicode():
    1162                  print u_link_symbol * 10
    1163          #-----------------------------------------------------------------------
    1164 -        def test_xml_escape():
    1165                  print xml_escape_string(u'<')
    1166                  print xml_escape_string(u'>')
    1167                  print xml_escape_string(u'&')
    1168          #-----------------------------------------------------------------------
    1169 -        def test_tex_escape():
    1170                  tests = [u'\\', u'^', u'~', u'{', u'}', u'%',  u'&', u'#', u'$', u'_', u_euro]
    1171                  tests.append(u'  '.join(tests))
    1172                  for test in tests:
    1173                          print u'%s:' % test, tex_escape_string(test)
    1174          #-----------------------------------------------------------------------
    1175 -        def test_gpg_decrypt():
    1176                  fname = gpg_decrypt_file(filename = sys.argv[2], passphrase = sys.argv[3])
    1177                  if fname is not None:
    1178                          print "successfully decrypted:", fname
    1179          #-----------------------------------------------------------------------
    1180 -        def test_strip_trailing_empty_lines():
    1181                  tests = [
    1182                          u'one line, no embedded line breaks  ',
    1183                          u'one line\nwith embedded\nline\nbreaks\n   '
    1184                  ]
    1185                  for test in tests:
    1186                          print 'as list:'
    1187                          print strip_trailing_empty_lines(text = test, eol=u'\n', return_list = True)
    1188                          print 'as string:'
    1189                          print u'>>>%s<<<' % strip_trailing_empty_lines(text = test, eol=u'\n', return_list = False)
    1190                  tests = [
    1191                          ['list', 'without', 'empty', 'trailing', 'lines'],
    1192                          ['list', 'with', 'empty', 'trailing', 'lines', '', '  ', '']
    1193                  ]
    1194                  for test in tests:
    1195                          print 'as list:'
    1196                          print strip_trailing_empty_lines(lines = test, eol = u'\n', return_list = True)
    1197                          print 'as string:'
    1198                          print strip_trailing_empty_lines(lines = test, eol = u'\n', return_list = False)
    1199          #-----------------------------------------------------------------------
    1200 -        def test_fname_stem():
    1201                  tests = [
    1202                          r'abc.exe',
    1203                          r'\abc.exe',
    1204                          r'c:\abc.exe',
    1205                          r'c:\d\abc.exe',
    1206                          r'/home/ncq/tmp.txt',
    1207                          r'~/tmp.txt',
    1208                          r'./tmp.txt',
    1209                          r'./.././tmp.txt',
    1210                          r'tmp.txt'
    1211                  ]
    1212                  for t in tests:
    1213                          print "[%s] -> [%s]" % (t, fname_stem(t))
    1214          #-----------------------------------------------------------------------
    1215          #test_coalesce()
    1216          #test_capitalize()
    1217          #test_import_module()
    1218          #test_mkdir()
    1219          #test_gmPaths()
    1220          #test_none_if()
    1221          #test_bool2str()
    1222          #test_bool2subst()
    1223          #test_get_unique_filename()
    1224          #test_size2str()
    1225          #test_wrap()
    1226          #test_input2decimal()
    1227          #test_input2int()
    1228          #test_unwrap()
    1229          #test_md5()
    1230          #test_unicode()
    1231          #test_xml_escape()
    1232          #test_gpg_decrypt()
    1233          #test_strip_trailing_empty_lines()
    1234          test_fname_stem()
    1235          #test_tex_escape()
    1236
    1237  #===========================================================================
    1238