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