# -*- coding: utf8 -*-
__doc__ = """GNUmed general tools."""
#===========================================================================
# $Id: gmTools.py,v 1.75.2.3 2009/04/21 17:04:23 ncq Exp $
# $Source: /sources/gnumed/gnumed/gnumed/client/pycommon/gmTools.py,v $
__version__ = "$Revision: 1.75.2.3 $"
__author__ = "K. Hilbert
"
__license__ = "GPL (details at http://www.gnu.org)"
# std libs
import datetime as pydt, re as regex, sys, os, os.path, csv, tempfile, logging, codecs, urllib2 as wget, decimal
# GNUmed libs
if __name__ == '__main__':
# for testing:
logging.basicConfig(level = logging.DEBUG)
sys.path.insert(0, '../../')
from Gnumed.pycommon import gmI18N
gmI18N.activate_locale()
gmI18N.install_domain()
from Gnumed.pycommon import gmBorg
_log = logging.getLogger('gm.tools')
_log.info(__version__)
# CAPitalization modes:
( CAPS_NONE, # don't touch it
CAPS_FIRST, # CAP first char, leave rest as is
CAPS_ALLCAPS, # CAP all chars
CAPS_WORDS, # CAP first char of every word
CAPS_NAMES, # CAP in a way suitable for names (tries to be smart)
CAPS_FIRST_ONLY # CAP first char, lowercase the rest
) = range(6)
default_mail_sender = u'address@hidden'
default_mail_receiver = u'address@hidden'
default_mail_server = u'mail.gmx.net'
u_registered_trademark = u'\u00ae'
u_plus_minus = u'\u00B1'
u_ellipsis = u'\u2026'
u_left_arrow = u'\u2190'
u_diameter = u'\u2300'
u_checkmark_crossed_out = u'\u237B'
u_checkmark_thin = u'\u2713'
u_checkmark_thick = u'\u2714'
u_writing_hand = u'\u270d'
u_pencil_1 = u'\u270e'
u_pencil_2 = u'\u270f'
u_pencil_3 = u'\u2710'
#===========================================================================
def check_for_update(url=None, current_branch=None, current_version=None, consider_latest_branch=False):
"""Check for new releases at .
Returns (bool, text).
True: new release available
False: up to date
None: don't know
"""
try:
remote_file = wget.urlopen(url)
except (wget.URLError, ValueError, OSError):
_log.exception("cannot retrieve version file from [%s]", url)
return (None, _('Cannot retrieve version information from:\n\n%s') % url)
_log.debug('retrieving version information from [%s]', url)
from Gnumed.pycommon import gmCfg2
cfg = gmCfg2.gmCfgData()
cfg.add_stream_source(source = 'gm-versions', stream = remote_file)
remote_file.close()
latest_branch = cfg.get('latest branch', 'branch', source_order = [('gm-versions', 'return')])
latest_release_on_latest_branch = cfg.get('branch %s' % latest_branch, 'latest release', source_order = [('gm-versions', 'return')])
latest_release_on_current_branch = cfg.get('branch %s' % current_branch, 'latest release', source_order = [('gm-versions', 'return')])
_log.info('current release: %s', current_version)
_log.info('current branch: %s', current_branch)
_log.info('latest release on current branch: %s', latest_release_on_current_branch)
_log.info('latest branch: %s', latest_branch)
_log.info('latest release on latest branch: %s', latest_release_on_latest_branch)
# anything known ?
no_release_information_available = (
(
(latest_release_on_current_branch is None) and
(latest_release_on_latest_branch is None)
) or (
not consider_latest_branch and
(latest_release_on_current_branch is None)
)
)
if no_release_information_available:
_log.warning('no release information available')
msg = _('There is no version information available from:\n\n%s') % url
return (None, msg)
# up to date ?
if consider_latest_branch:
_log.debug('latest branch taken into account')
if current_version >= latest_release_on_latest_branch:
_log.debug('up to date: current version >= latest version on latest branch')
return (False, None)
if latest_release_on_latest_branch is None:
if current_version >= latest_release_on_current_branch:
_log.debug('up to date: current version >= latest version on current branch and no latest branch available')
return (False, None)
else:
_log.debug('latest branch not taken into account')
if current_version >= latest_release_on_current_branch:
_log.debug('up to date: current version >= latest version on current branch')
return (False, None)
new_release_on_current_branch_available = (
(latest_release_on_current_branch is not None) and
(latest_release_on_current_branch > current_version)
)
_log.info('%snew release on current branch available', bool2str(new_release_on_current_branch_available, '', 'no '))
new_release_on_latest_branch_available = (
(latest_branch is not None)
and
(
(latest_branch > current_branch) or (
(latest_branch == current_branch) and
(latest_release_on_latest_branch > current_version)
)
)
)
_log.info('%snew release on latest branch available', bool2str(new_release_on_latest_branch_available, '', 'no '))
if not (new_release_on_current_branch_available or new_release_on_latest_branch_available):
_log.debug('up to date: no new releases available')
return (False, None)
# not up to date
msg = _('A new version of GNUmed is available.\n\n')
msg += _(' Your current version: "%s"\n') % current_version
if consider_latest_branch:
if new_release_on_current_branch_available:
msg += u'\n'
msg += _(' New version: "%s"') % latest_release_on_current_branch
msg += u'\n'
msg += _(' - bug fixes only\n')
msg += _(' - no database upgrade needed\n')
if new_release_on_latest_branch_available:
if current_branch != latest_branch:
msg += u'\n'
msg += _(' New version: "%s"') % latest_release_on_latest_branch
msg += u'\n'
msg += _(' - bug fixes and new features\n')
msg += _(' - database upgrade required\n')
else:
msg += u'\n'
msg += _(' New version: "%s"') % latest_release_on_current_branch
msg += u'\n'
msg += _(' - bug fixes only\n')
msg += _(' - no database upgrade needed\n')
msg += u'\n\n'
msg += _(
'Note, however, that this version may not yet\n'
'be available *pre-packaged* for your system.'
)
msg += u'\n\n'
msg += _('Version information loaded from:\n\n %s') % url
return (True, msg)
#===========================================================================
def utf_8_encoder(unicode_csv_data):
for line in unicode_csv_data:
yield line.encode('utf-8')
def unicode_csv_reader(unicode_csv_data, dialect=csv.excel, **kwargs):
# csv.py doesn't do Unicode; encode temporarily as UTF-8:
csv_reader = csv.reader(utf_8_encoder(unicode_csv_data), dialect=dialect, **kwargs)
for row in csv_reader:
# decode UTF-8 back to Unicode, cell by cell:
yield [unicode(cell, 'utf-8') for cell in row]
#===========================================================================
def handle_uncaught_exception_console(t, v, tb):
print ",========================================================"
print "| Unhandled exception caught !"
print "| Type :", t
print "| Value:", v
print "`========================================================"
_log.critical('unhandled exception caught', exc_info = (t,v,tb))
sys.__excepthook__(t,v,tb)
#===========================================================================
class gmPaths(gmBorg.cBorg):
def __init__(self, app_name=None, wx=None):
"""Setup pathes.
will default to (name of the script - .py)
"""
try:
self.already_inited
return
except AttributeError:
pass
self.init_paths(app_name=app_name, wx=wx)
self.already_inited = True
#--------------------------------------
# public API
#--------------------------------------
def init_paths(self, app_name=None, wx=None):
if app_name is None:
app_name, ext = os.path.splitext(os.path.basename(sys.argv[0]))
_log.info('app name detected as [%s]', app_name)
else:
_log.info('app name passed in as [%s]', app_name)
self.local_base_dir = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '..', '.'))
self.working_dir = os.path.abspath(os.curdir)
try:
self.user_config_dir = os.path.expanduser(os.path.join('~', '.%s' % app_name))
except ValueError:
mkdir(os.path.expanduser(os.path.join('~', '.%s' % app_name)))
self.user_config_dir = os.path.expanduser(os.path.join('~', '.%s' % app_name))
try:
self.system_config_dir = os.path.join('/etc', app_name)
except ValueError:
self.system_config_dir = self.local_base_dir
try:
self.system_app_data_dir = os.path.join(sys.prefix, 'share', app_name)
except ValueError:
self.system_app_data_dir = self.local_base_dir
if wx is None:
_log.debug('wxPython not available')
self.__log_paths()
return True
# retry with wxPython
std_paths = wx.StandardPaths.Get()
_log.info('wxPython app name set to [%s]', wx.GetApp().GetAppName())
try:
self.user_config_dir = os.path.join(std_paths.GetUserConfigDir(), '.%s' % app_name)
except ValueError:
mkdir(os.path.join(std_paths.GetUserConfigDir(), '.%s' % app_name))
self.user_config_dir = os.path.join(std_paths.GetUserConfigDir(), '.%s' % app_name)
try:
tmp = std_paths.GetConfigDir()
if not tmp.endswith(app_name):
tmp = os.path.join(tmp, app_name)
self.system_config_dir = tmp
except ValueError:
pass
# Robin attests that the following gives existing
# but faulty paths on Windows, so IFDEF it
if 'wxMSW' in wx.PlatformInfo:
_log.warning('this platform (wxMSW) returns a broken value for the system-wide application data dir')
else:
try:
self.system_app_data_dir = std_paths.GetDataDir()
except ValueError:
pass
self.__log_paths()
return True
#--------------------------------------
def __log_paths(self):
_log.debug('local application base dir: %s', self.local_base_dir)
_log.debug('current working dir: %s', self.working_dir)
_log.debug('user home dir: %s', os.path.expanduser('~'))
_log.debug('user-specific config dir: %s', self.user_config_dir)
_log.debug('system-wide config dir: %s', self.system_config_dir)
_log.debug('system-wide application data dir: %s', self.system_app_data_dir)
#--------------------------------------
# properties
#--------------------------------------
def _set_user_config_dir(self, path):
if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)):
msg = '[%s:user_config_dir]: invalid path [%s]' % (self.__class__.__name__, path)
_log.error(msg)
raise ValueError(msg)
self.__user_config_dir = path
def _get_user_config_dir(self):
return self.__user_config_dir
user_config_dir = property(_get_user_config_dir, _set_user_config_dir)
#--------------------------------------
def _set_system_config_dir(self, path):
if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)):
msg = '[%s:system_config_dir]: invalid path [%s]' % (self.__class__.__name__, path)
_log.error(msg)
raise ValueError(msg)
self.__system_config_dir = path
def _get_system_config_dir(self):
return self.__system_config_dir
system_config_dir = property(_get_system_config_dir, _set_system_config_dir)
#--------------------------------------
def _set_system_app_data_dir(self, path):
if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)):
msg = '[%s:system_app_data_dir]: invalid path [%s]' % (self.__class__.__name__, path)
_log.error(msg)
raise ValueError(msg)
self.__system_app_data_dir = path
def _get_system_app_data_dir(self):
return self.__system_app_data_dir
system_app_data_dir = property(_get_system_app_data_dir, _set_system_app_data_dir)
#===========================================================================
def send_mail(sender=None, receiver=None, message=None, server=None, auth=None, debug=False, subject=None, encoding='latin1'):
"""Send an E-Mail.
: see smtplib.set_debuglevel()
: {'user': ..., 'password': ...}
: a list of email addresses
"""
if message is None:
return False
message = message.lstrip().lstrip('\r\n').lstrip()
if sender is None:
sender = default_mail_sender
if receiver is None:
receiver = [default_mail_receiver]
if server is None:
server = default_mail_server
if subject is None:
subject = u'gmTools.py: send_mail() test'
body = u"""From: %s
To: %s
Subject: %s
%s
""" % (sender, u', '.join(receiver), subject[:50].replace('\r', '/').replace('\n', '/'), message)
import smtplib
session = smtplib.SMTP(server)
session.set_debuglevel(debug)
if auth is not None:
session.login(auth['user'], auth['password'])
refused = session.sendmail(sender, receiver, body.encode(encoding))
session.quit()
if len(refused) != 0:
_log.error("refused recipients: %s" % refused)
return False
return True
#===========================================================================
def mkdir(directory=None):
try:
os.makedirs(directory)
except OSError, e:
if (e.errno == 17) and not os.path.isdir(directory):
raise
return True
#---------------------------------------------------------------------------
def get_unique_filename(prefix=None, suffix=None, tmp_dir=None):
"""This introduces a race condition between the file.close() and
actually using the filename.
The file will not exist after calling this function.
"""
if tmp_dir is not None:
if (
not os.access(tmp_dir, os.F_OK)
or
not os.access(tmp_dir, os.X_OK | os.W_OK)
):
_log.info('cannot use temporary dir [%s]', tmp_dir)
tmp_dir = None
kwargs = {'dir': tmp_dir}
if prefix is None:
kwargs['prefix'] = 'gnumed-'
else:
kwargs['prefix'] = prefix
if suffix is None:
kwargs['suffix'] = '.tmp'
else:
if not suffix.startswith('.'):
suffix = '.' + suffix
kwargs['suffix'] = suffix
f = tempfile.NamedTemporaryFile(**kwargs)
filename = f.name
f.close()
return filename
#===========================================================================
def import_module_from_directory(module_path=None, module_name=None):
"""Import a module from any location."""
if module_path not in sys.path:
_log.info('appending to sys.path: [%s]' % module_path)
sys.path.append(module_path)
remove_path = True
else:
remove_path = False
if module_name.endswith('.py'):
module_name = module_name[:-3]
try:
module = __import__(module_name)
except StandardError:
_log.exception('cannot __import__() module [%s] from [%s]' % (module_name, module_path))
sys.path.remove(module_path)
raise
_log.info('imported module [%s] as [%s]' % (module_name, module))
if remove_path:
sys.path.remove(module_path)
return module
#===========================================================================
# FIXME: should this not be in gmTime or some such?
# close enough on average
days_per_year = 365
days_per_month = 30
days_per_week = 7
#---------------------------------------------------------------------------
def str2interval(str_interval=None):
unit_keys = {
'year': _('yYaA_keys_year'),
'month': _('mM_keys_month'),
'week': _('wW_keys_week'),
'day': _('dD_keys_day'),
'hour': _('hH_keys_hour')
}
# "(~)35(yY)" - at age 35 years
keys = '|'.join(list(unit_keys['year'].replace('_keys_year', u'')))
if regex.match(u'^(\s|\t)*~*(\s|\t)*\d+(%s|\s|\t)*$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE):
return pydt.timedelta(days = (int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]) * days_per_year))
# "(~)12mM" - at age 12 months
keys = '|'.join(list(unit_keys['month'].replace('_keys_month', u'')))
if regex.match(u'^(\s|\t)*~*(\s|\t)*\d+(\s|\t)*(%s)+(\s|\t)*$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE):
years, months = divmod (
int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]),
12
)
return pydt.timedelta(days = ((years * days_per_year) + (months * days_per_month)))
# weeks
keys = '|'.join(list(unit_keys['week'].replace('_keys_week', u'')))
if regex.match(u'^(\s|\t)*~*(\s|\t)*\d+(\s|\t)*(%s)+(\s|\t)*$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE):
return pydt.timedelta(weeks = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
# days
keys = '|'.join(list(unit_keys['day'].replace('_keys_day', u'')))
if regex.match(u'^(\s|\t)*~*(\s|\t)*\d+(\s|\t)*(%s)+(\s|\t)*$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE):
return pydt.timedelta(days = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
# hours
keys = '|'.join(list(unit_keys['hour'].replace('_keys_hour', u'')))
if regex.match(u'^(\s|\t)*~*(\s|\t)*\d+(\s|\t)*(%s)+(\s|\t)*$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE):
return pydt.timedelta(hours = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
# x/12 - months
if regex.match(u'^(\s|\t)*~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*12(\s|\t)*$', str_interval, flags = regex.LOCALE | regex.UNICODE):
years, months = divmod (
int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]),
12
)
return pydt.timedelta(days = ((years * days_per_year) + (months * days_per_month)))
# x/52 - weeks
if regex.match(u'^(\s|\t)*~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*52(\s|\t)*$', str_interval, flags = regex.LOCALE | regex.UNICODE):
# return pydt.timedelta(days = (int(regex.findall('\d+', str_interval)[0]) * days_per_week))
return pydt.timedelta(weeks = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
# x/7 - days
if regex.match(u'^(\s|\t)*~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*7(\s|\t)*$', str_interval, flags = regex.LOCALE | regex.UNICODE):
return pydt.timedelta(days = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
# x/24 - hours
if regex.match(u'^(\s|\t)*~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*24(\s|\t)*$', str_interval, flags = regex.LOCALE | regex.UNICODE):
return pydt.timedelta(hours = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
# x/60 - minutes
if regex.match(u'^(\s|\t)*~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*60(\s|\t)*$', str_interval, flags = regex.LOCALE | regex.UNICODE):
return pydt.timedelta(minutes = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
# nYnM - years, months
keys_year = '|'.join(list(unit_keys['year'].replace('_keys_year', u'')))
keys_month = '|'.join(list(unit_keys['month'].replace('_keys_month', u'')))
if regex.match(u'^(\s|\t)*~*(\s|\t)*\d+(%s|\s|\t)+\d+(\s|\t)*(%s)+(\s|\t)*$' % (keys_year, keys_month), str_interval, flags = regex.LOCALE | regex.UNICODE):
parts = regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)
years, months = divmod(int(parts[1]), 12)
years += int(parts[0])
return pydt.timedelta(days = ((years * days_per_year) + (months * days_per_month)))
# nMnW - months, weeks
keys_month = '|'.join(list(unit_keys['month'].replace('_keys_month', u'')))
keys_week = '|'.join(list(unit_keys['week'].replace('_keys_week', u'')))
if regex.match(u'^(\s|\t)*~*(\s|\t)*\d+(%s|\s|\t)+\d+(\s|\t)*(%s)+(\s|\t)*$' % (keys_month, keys_week), str_interval, flags = regex.LOCALE | regex.UNICODE):
parts = regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)
months, weeks = divmod(int(parts[1]), 4)
months += int(parts[0])
return pydt.timedelta(days = ((months * days_per_month) + (weeks * days_per_week)))
return None
#===========================================================================
# text related tools
#---------------------------------------------------------------------------
_kB = 1024
_MB = 1024 * _kB
_GB = 1024 * _MB
_TB = 1024 * _GB
_PB = 1024 * _TB
#---------------------------------------------------------------------------
def size2str(size=0, template='%s'):
if size == 1:
return template % _('1 Byte')
if size < 10 * _kB:
return template % _('%s Bytes') % size
if size < _MB:
return template % u'%.1f kB' % (float(size) / _kB)
if size < _GB:
return template % u'%.1f MB' % (float(size) / _MB)
if size < _TB:
return template % u'%.1f GB' % (float(size) / _GB)
if size < _PB:
return template % u'%.1f TB' % (float(size) / _TB)
return template % u'%.1f PB' % (float(size) / _PB)
#---------------------------------------------------------------------------
def bool2subst(boolean=None, true_return=True, false_return=False, none_return=None):
if boolean is None:
return none_return
if boolean is True:
return true_return
if boolean is False:
return false_return
raise ValueError('bool2subst(): arg must be either of True, False, None')
#---------------------------------------------------------------------------
def bool2str(boolean=None, true_str='True', false_str='False'):
return bool2subst (
boolean = bool(boolean),
true_return = true_str,
false_return = false_str
)
#---------------------------------------------------------------------------
def none_if(value=None, none_equivalent=None):
"""Modelled after the SQL NULLIF function."""
if value == none_equivalent:
return None
return value
#---------------------------------------------------------------------------
def coalesce(initial=None, instead=None, template_initial=None, template_instead=None):
"""Modelled after the SQL coalesce function.
To be used to simplify constructs like:
if value is None:
real_value = some_other_value
else:
real_value = some_template_with_%s_formatter % value
print real_value
@param initial: the value to be tested for
@type initial: any Python type, must have a __str__ method if template_initial is not None
@param instead: the value to be returned if is None
@type instead: any Python type, must have a __str__ method if template_instead is not None
@param template_initial: if is returned replace the value into this template, must contain one <%s>
@type template_initial: string or None
@param template_instead: if is returned replace the value into this template, must contain one <%s>
@type template_instead: string or None
Ideas:
- list of None-equivalents
- list of insteads: initial, [instead, template], [instead, template], [instead, template], template_initial, ...
"""
if initial is None:
if template_instead is None:
return instead
return template_instead % instead
if template_initial is None:
return initial
try:
return template_initial % initial
except TypeError:
return template_initial
#---------------------------------------------------------------------------
def __cap_name(match_obj=None):
val = match_obj.group(0).lower()
if val in ['von', 'van', 'de', 'la', 'l', 'der', 'den']: # FIXME: this needs to expand, configurable ?
return val
buf = list(val)
buf[0] = buf[0].upper()
for part in ['mac', 'mc', 'de', 'la']:
if len(val) > len(part) and val[:len(part)] == part:
buf[len(part)] = buf[len(part)].upper()
return ''.join(buf)
#---------------------------------------------------------------------------
def capitalize(text=None, mode=CAPS_NAMES):
"""Capitalize the first character but leave the rest alone.
Note that we must be careful about the locale, this may
have issues ! However, for UTF strings it should just work.
"""
if (mode is None) or (mode == CAPS_NONE):
return text
if mode == CAPS_FIRST:
if len(text) == 1:
return text[0].upper()
return text[0].upper() + text[1:]
if mode == CAPS_ALLCAPS:
return text.upper()
if mode == CAPS_FIRST_ONLY:
if len(text) == 1:
return text[0].upper()
return text[0].upper() + text[1:].lower()
if mode == CAPS_WORDS:
return regex.sub(ur'(\w)(\w+)', lambda x: x.group(1).upper() + x.group(2).lower(), text)
if mode == CAPS_NAMES:
#return regex.sub(r'\w+', __cap_name, text)
return capitalize(text=text, mode=CAPS_FIRST) # until fixed
print "ERROR: invalid capitalization mode: [%s], leaving input as is" % mode
return text
#---------------------------------------------------------------------------
def input2decimal(initial=None):
val = initial
# float ? -> to string first
if type(val) == type(1.4):
val = str(val)
# string ? -> "," to "."
if isinstance(val, basestring):
val = val.replace(',', '.', 1)
val = val.strip()
# val = val.lstrip('0')
# if val.startswith('.'):
# val = '0' + val
try:
d = decimal.Decimal(val)
return True, d
except (TypeError, decimal.InvalidOperation):
return False, val
#---------------------------------------------------------------------------
def wrap(text=None, width=None, initial_indent=u'', subsequent_indent=u'', eol=u'\n'):
"""A word-wrap function that preserves existing line breaks
and most spaces in the text. Expects that existing line
breaks are posix newlines (\n).
"""
wrapped = initial_indent + reduce (
lambda line, word, width=width: '%s%s%s' % (
line,
' \n'[(len(line) - line.rfind('\n') - 1 + len(word.split('\n',1)[0]) >= width)],
word
),
text.split(' ')
)
if subsequent_indent != u'':
wrapped = (u'\n%s' % subsequent_indent).join(wrapped.split('\n'))
if eol != u'\n':
wrapped = wrapped.replace('\n', eol)
return wrapped
#===========================================================================
# main
#---------------------------------------------------------------------------
if __name__ == '__main__':
#-----------------------------------------------------------------------
def test_input2decimal():
tests = [
[None, False],
['', False],
[' 0 ', True, 0],
[0, True, 0],
[0.0, True, 0],
[.0, True, 0],
['0', True, 0],
['0.0', True, 0],
['0,0', True, 0],
['00.0', True, 0],
['.0', True, 0],
[',0', True, 0],
[0.1, True, decimal.Decimal('0.1')],
[.01, True, decimal.Decimal('0.01')],
['0.1', True, decimal.Decimal('0.1')],
['0,1', True, decimal.Decimal('0.1')],
['00.1', True, decimal.Decimal('0.1')],
['.1', True, decimal.Decimal('0.1')],
[',1', True, decimal.Decimal('0.1')],
[1, True, 1],
[1.0, True, 1],
['1', True, 1],
['1.', True, 1],
['1,', True, 1],
['1.0', True, 1],
['1,0', True, 1],
['01.0', True, 1],
['01,0', True, 1],
[' 01, ', True, 1],
]
for test in tests:
conversion_worked, result = input2decimal(initial = test[0])
expected2work = test[1]
if conversion_worked:
if expected2work:
if result == test[2]:
continue
else:
print "ERROR (conversion result wrong): >%s<, expected >%s<, got >%s<" % (test[0], test[2], result)
else:
print "ERROR (conversion worked but was expected to fail): >%s<, got >%s<" % (test[0], result)
else:
if not expected2work:
continue
else:
print "ERROR (conversion failed but was expected to work): >%s<, expected >%s<" % (test[0], test[2])
#-----------------------------------------------------------------------
def test_str2interval():
print "testing str2interval()"
print "----------------------"
str_intervals = [
'7', '12', ' 12', '12 ', ' 12 ', ' 12 ', '0', '~12', '~ 12', ' ~ 12', ' ~ 12 ',
'12a', '12 a', '12 a', '12j', '12J', '12y', '12Y', ' ~ 12 a ', '~0a',
'12m', '17 m', '12 m', '17M', ' ~ 17 m ', ' ~ 3 / 12 ', '7/12', '0/12',
'12w', '17 w', '12 w', '17W', ' ~ 17 w ', ' ~ 15 / 52', '2/52', '0/52',
'12d', '17 d', '12 t', '17D', ' ~ 17 T ', ' ~ 12 / 7', '3/7', '0/7',
'12h', '17 h', '12 H', '17H', ' ~ 17 h ', ' ~ 36 / 24', '7/24', '0/24',
' ~ 36 / 60', '7/60', '190/60', '0/60',
'12a1m', '12 a 1 M', '12 a17m', '12j 12m', '12J7m', '12y7m', '12Y7M', ' ~ 12 a 37 m ', '~0a0m',
'10m1w',
'invalid interval input'
]
for str_interval in str_intervals:
print "input: <%s>" % str_interval
print " ==>", str2interval(str_interval=str_interval)
return True
#-----------------------------------------------------------------------
def test_coalesce():
print 'testing coalesce()'
print "------------------"
tests = [
[None, 'something other than ', None, None, 'something other than '],
['Captain', 'Mr.', '%s.'[:4], 'Mr.', 'Capt.'],
['value to test', 'test 3 failed', 'template with "%s" included', None, 'template with "value to test" included'],
['value to test', 'test 4 failed', 'template with value not included', None, 'template with value not included'],
[None, 'initial value was None', 'template_initial: %s', None, 'initial value was None'],
[None, 'initial value was None', 'template_initial: %%(abc)s', None, 'initial value was None']
]
passed = True
for test in tests:
result = coalesce (
initial = test[0],
instead = test[1],
template_initial = test[2],
template_instead = test[3]
)
if result != test[4]:
print "ERROR"
print "coalesce: (%s, %s, %s, %s)" % (test[0], test[1], test[2], test[3])
print "expected:", test[4]
print "received:", result
passed = False
if passed:
print "passed"
else:
print "failed"
return passed
#-----------------------------------------------------------------------
def test_capitalize():
print 'testing capitalize() ...'
success = True
pairs = [
# [original, expected result, CAPS mode]
[u'Boot', u'Boot', CAPS_FIRST_ONLY],
[u'boot', u'Boot', CAPS_FIRST_ONLY],
[u'booT', u'Boot', CAPS_FIRST_ONLY],
[u'BoOt', u'Boot', CAPS_FIRST_ONLY],
[u'boots-Schau', u'Boots-Schau', CAPS_WORDS],
[u'boots-sChau', u'Boots-Schau', CAPS_WORDS],
[u'boot camp', u'Boot Camp', CAPS_WORDS],
[u'fahrner-Kampe', u'Fahrner-Kampe', CAPS_NAMES],
[u'häkkönen', u'Häkkönen', CAPS_NAMES],
[u'McBurney', u'McBurney', CAPS_NAMES],
[u'mcBurney', u'McBurney', CAPS_NAMES],
[u'blumberg', u'Blumberg', CAPS_NAMES],
[u'roVsing', u'RoVsing', CAPS_NAMES],
[u'Özdemir', u'Özdemir', CAPS_NAMES],
[u'özdemir', u'Özdemir', CAPS_NAMES],
]
for pair in pairs:
result = capitalize(pair[0], pair[2])
if result != pair[1]:
success = False
print 'ERROR (caps mode %s): "%s" -> "%s", expected "%s"' % (pair[2], pair[0], result, pair[1])
if success:
print "... SUCCESS"
return success
#-----------------------------------------------------------------------
def test_import_module():
print "testing import_module_from_directory()"
path = sys.argv[1]
name = sys.argv[2]
try:
mod = import_module_from_directory(module_path = path, module_name = name)
except:
print "module import failed, see log"
return False
print "module import succeeded", mod
print dir(mod)
return True
#-----------------------------------------------------------------------
def test_mkdir():
print "testing mkdir()"
mkdir(sys.argv[1])
#-----------------------------------------------------------------------
def test_send_mail():
msg = u"""
To: %s
From: %s
Subject: gmTools test suite mail
This is a test mail from the gmTools.py module.
""" % (default_mail_receiver, default_mail_sender)
print "mail sending succeeded:", send_mail (
receiver = [default_mail_receiver, u'address@hidden'],
message = msg,
auth = {'user': default_mail_sender, 'password': u'gm/bugs/gmx'},
debug = True
)
#-----------------------------------------------------------------------
def test_gmPaths():
print "testing gmPaths()"
print "-----------------"
paths = gmPaths(wx=None, app_name='gnumed')
print "user config dir:", paths.user_config_dir
print "system config dir:", paths.system_config_dir
print "local base dir:", paths.local_base_dir
print "system app data dir:", paths.system_app_data_dir
print "working directory :", paths.working_dir
#-----------------------------------------------------------------------
def test_none_if():
print "testing none_if()"
print "-----------------"
tests = [
[None, None, None],
['a', 'a', None],
['a', 'b', 'a'],
['a', None, 'a'],
[None, 'a', None],
[1, 1, None],
[1, 2, 1],
[1, None, 1],
[None, 1, None]
]
for test in tests:
if none_if(value = test[0], none_equivalent = test[1]) != test[2]:
print 'ERROR: none_if(%s) returned [%s], expected [%s]' % (test[0], none_if(test[0], test[1]), test[2])
return True
#-----------------------------------------------------------------------
def test_bool2str():
tests = [
[True, 'Yes', 'Yes', 'Yes'],
[False, 'OK', 'not OK', 'not OK']
]
for test in tests:
if bool2str(test[0], test[1], test[2]) != test[3]:
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])
return True
#-----------------------------------------------------------------------
def test_bool2subst():
print bool2subst(True, 'True', 'False', 'is None')
print bool2subst(False, 'True', 'False', 'is None')
print bool2subst(None, 'True', 'False', 'is None')
#-----------------------------------------------------------------------
def test_get_unique_filename():
print get_unique_filename()
print get_unique_filename(prefix='test-')
print get_unique_filename(suffix='tst')
print get_unique_filename(prefix='test-', suffix='tst')
print get_unique_filename(tmp_dir='/home/ncq/Archiv/')
#-----------------------------------------------------------------------
def test_size2str():
print "testing size2str()"
print "------------------"
tests = [0, 1, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000, 1000000000000, 10000000000000]
for test in tests:
print size2str(test)
#-----------------------------------------------------------------------
def test_wrap():
test = 'line 1\nline 2\nline 3'
print "wrap 5-6-7 initial 0, subsequent 0"
print wrap(test, 5)
print
print wrap(test, 6)
print
print wrap(test, 7)
print "-------"
raw_input()
print "wrap 5 initial 1-1-3, subsequent 1-3-1"
print wrap(test, 5, u' ', u' ')
print
print wrap(test, 5, u' ', u' ')
print
print wrap(test, 5, u' ', u' ')
print "-------"
raw_input()
print "wrap 6 initial 1-1-3, subsequent 1-3-1"
print wrap(test, 6, u' ', u' ')
print
print wrap(test, 6, u' ', u' ')
print
print wrap(test, 6, u' ', u' ')
print "-------"
raw_input()
print "wrap 7 initial 1-1-3, subsequent 1-3-1"
print wrap(test, 7, u' ', u' ')
print
print wrap(test, 7, u' ', u' ')
print
print wrap(test, 7, u' ', u' ')
#-----------------------------------------------------------------------
def test_check_for_update():
test_data = [
('http://www.gnumed.de/downloads/gnumed-versions.txt', None, None, False),
('file:///home/ncq/gm-versions.txt', None, None, False),
('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.1', False),
('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.1', True),
('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.5', True)
]
for test in test_data:
print "arguments:", test
found, msg = check_for_update(test[0], test[1], test[2], test[3])
print msg
return
#-----------------------------------------------------------------------
if len(sys.argv) > 1 and sys.argv[1] == 'test':
#test_check_for_update()
#test_str2interval()
#test_coalesce()
#test_capitalize()
#test_import_module()
#test_mkdir()
#test_send_mail()
#test_gmPaths()
#test_none_if()
#test_bool2str()
#test_bool2subst()
#test_get_unique_filename()
#test_size2str()
#test_wrap()
test_input2decimal()
#===========================================================================
# $Log: gmTools.py,v $
# Revision 1.75.2.3 2009/04/21 17:04:23 ncq
# - fix setting sys app data dir on non-Windows
#
# Revision 1.75.2.2 2009/04/20 12:04:13 ncq
# - back to previous behaviour for app data dir on Windows
#
# Revision 1.75.2.1 2009/03/24 10:31:24 ncq
# - improved app data dir on Windows
#
# Revision 1.75 2009/03/01 18:10:50 ncq
# - improve update-avail message
#
# Revision 1.74 2009/02/18 13:45:25 ncq
# - get_unique_filename API change
#
# Revision 1.73 2009/01/02 11:38:09 ncq
# - input2decimal + tests
#
# Revision 1.72 2008/12/22 18:58:53 ncq
# - cleanup
#
# Revision 1.71 2008/12/09 23:28:15 ncq
# - better logging
# - always remove aux module path if importing fails
#
# Revision 1.70 2008/11/20 18:47:40 ncq
# - add left arrow unicode
# - fix logging in update check
#
# Revision 1.69 2008/11/03 10:28:55 ncq
# - check_for_update
# - improved logging and wording
# - logic reversal fix
#
# Revision 1.68 2008/10/12 15:48:33 ncq
# - improved wording when checking for updates
#
# Revision 1.67 2008/08/31 16:13:15 ncq
# - cleanup
#
# Revision 1.66 2008/08/28 18:32:24 ncq
# - read latest branch then latest release from branch group
#
# Revision 1.65 2008/08/20 13:53:57 ncq
# - add some coalesce tests
#
# Revision 1.64 2008/07/28 15:43:35 ncq
# - teach wrap() about target EOL
#
# Revision 1.63 2008/07/12 15:30:56 ncq
# - improved coalesce test
#
# Revision 1.62 2008/07/12 15:24:37 ncq
# - impove coalesce to allow template_initial to be returned *instead* of
# initial substituted into the template by not including a substitution
#
# Revision 1.61 2008/07/10 20:51:38 ncq
# - better logging
#
# Revision 1.60 2008/07/10 19:59:09 ncq
# - better logging
# - check whether sys config dir ends in "gnumed"
#
# Revision 1.59 2008/07/07 11:34:41 ncq
# - robustify capsify on single character strings
#
# Revision 1.58 2008/06/28 18:25:01 ncq
# - add unicode Registered TM symbol
#
# Revision 1.57 2008/05/31 16:32:42 ncq
# - a couple of unicode shortcuts
#
# Revision 1.56 2008/05/26 12:05:50 ncq
# - improved wording of update message
# - better handling of CVS tip
#
# Revision 1.55 2008/05/21 15:51:45 ncq
# - if cannot open update URL may throw OSError, so deal with that
#
# Revision 1.54 2008/05/21 14:01:32 ncq
# - add check_for_update and tests
#
# Revision 1.53 2008/05/13 14:09:36 ncq
# - str2interval: support xMxW syntax
#
# Revision 1.52 2008/05/07 15:18:01 ncq
# - i18n str2interval
#
# Revision 1.51 2008/04/16 20:34:43 ncq
# - add bool2subst tests
#
# Revision 1.50 2008/04/11 12:24:39 ncq
# - add initial_indent/subsequent_indent and tests to wrap()
#
# Revision 1.49 2008/03/20 15:29:51 ncq
# - bool2subst() supporting None, make bool2str() use it
#
# Revision 1.48 2008/03/02 15:10:32 ncq
# - truncate exception comment to 50 chars when used as subject
#
# Revision 1.47 2008/01/16 19:42:24 ncq
# - whitespace sync
#
# Revision 1.46 2007/12/23 11:59:40 ncq
# - improved docs
#
# Revision 1.45 2007/12/12 16:24:09 ncq
# - general cleanup
#
# Revision 1.44 2007/12/11 14:33:48 ncq
# - use standard logging module
#
# Revision 1.43 2007/11/28 13:59:23 ncq
# - test improved
#
# Revision 1.42 2007/11/21 13:28:35 ncq
# - enhance send_mail() with subject and encoding
# - handle body formatting
#
# Revision 1.41 2007/10/23 21:23:30 ncq
# - cleanup
#
# Revision 1.40 2007/10/09 10:29:02 ncq
# - clean up import_module_from_directory()
#
# Revision 1.39 2007/10/08 12:48:17 ncq
# - normalize / and \ in import_module_from_directory() so it works on Windows
#
# Revision 1.38 2007/08/29 14:33:56 ncq
# - better document get_unique_filename()
#
# Revision 1.37 2007/08/28 21:47:19 ncq
# - log user home dir
#
# Revision 1.36 2007/08/15 09:18:56 ncq
# - size2str() and test
#
# Revision 1.35 2007/08/07 21:41:02 ncq
# - cPaths -> gmPaths
#
# Revision 1.34 2007/07/13 09:47:38 ncq
# - fix and test suite for get_unique_filename()
#
# Revision 1.33 2007/07/11 21:06:51 ncq
# - improved docs
# - get_unique_filename()
#
# Revision 1.32 2007/07/10 20:45:42 ncq
# - add unicode CSV reader
# - factor out OOo related code
#
# Revision 1.31 2007/06/19 12:43:17 ncq
# - add bool2str() and test
#
# Revision 1.30 2007/06/10 09:56:03 ncq
# - u''ificiation and flags in regex calls
#
# Revision 1.29 2007/05/17 15:12:59 ncq
# - even more careful about pathes
#
# Revision 1.28 2007/05/17 15:10:16 ncq
# - create user config dir if it doesn't exist
#
# Revision 1.27 2007/05/15 08:20:13 ncq
# - ifdef GetDataDir() on wxMSW as per Robin's suggestion
#
# Revision 1.26 2007/05/14 08:35:06 ncq
# - better logging
# - try to handle platforms with broken GetDataDir()
#
# Revision 1.25 2007/05/13 21:20:54 ncq
# - improved logging
#
# Revision 1.24 2007/05/13 20:22:17 ncq
# - log errors
#
# Revision 1.23 2007/05/08 16:03:55 ncq
# - add console exception display handler
#
# Revision 1.22 2007/05/07 12:31:06 ncq
# - improved path handling and testing
# - switch file to utf8
#
# Revision 1.21 2007/04/21 19:38:27 ncq
# - add none_if() and test suite
#
# Revision 1.20 2007/04/19 13:09:52 ncq
# - add cPaths borg and test suite
#
# Revision 1.19 2007/04/09 16:30:31 ncq
# - add send_mail()
#
# Revision 1.18 2007/03/08 16:19:30 ncq
# - typo and cleanup
#
# Revision 1.17 2007/02/17 13:58:11 ncq
# - improved coalesce()
#
# Revision 1.16 2007/02/04 16:43:01 ncq
# - improve capitalize() test suite
# - set coding
#
# Revision 1.15 2007/02/04 16:29:51 ncq
# - make umlauts u''
#
# Revision 1.14 2007/02/04 15:33:28 ncq
# - enhance capitalize() and add mode CONSTS for it
# - however, CAPS_NAMES for now maps to CAPS_FIRST until fixed for Heller-Brunner
# - slightly improved test suite for it
#
# Revision 1.13 2007/01/30 17:38:28 ncq
# - add mkdir() and a test for it
#
# Revision 1.12 2007/01/20 22:04:01 ncq
# - strip ".py" from script name if it is there
#
# Revision 1.11 2007/01/18 12:46:30 ncq
# - add reasonably safe import_module_from_directory() and test
#
# Revision 1.10 2007/01/15 20:20:39 ncq
# - add wrap()
#
# Revision 1.9 2007/01/06 17:05:57 ncq
# - start OOo server if cannot connect to one
# - test suite
#
# Revision 1.8 2006/12/21 10:53:53 ncq
# - document coalesce() better
#
# Revision 1.7 2006/12/18 15:51:12 ncq
# - comment how to start server OOo writer
#
# Revision 1.6 2006/12/17 20:47:16 ncq
# - add open_uri_in_ooo()
#
# Revision 1.5 2006/11/27 23:02:08 ncq
# - add comment
#
# Revision 1.4 2006/11/24 09:52:09 ncq
# - add str2interval() - this will need to end up in an interval input phrasewheel !
# - improve test suite
#
# Revision 1.3 2006/11/20 15:58:10 ncq
# - template handling in coalesce()
#
# Revision 1.2 2006/10/31 16:03:06 ncq
# - add capitalize() and test
#
# Revision 1.1 2006/09/03 08:53:19 ncq
# - first version
#
#