1
2 __doc__ = """GNUmed general tools."""
3
4
5 __version__ = "$Revision: 1.98 $"
6 __author__ = "K. Hilbert <Karsten.Hilbert@gmx.net>"
7 __license__ = "GPL (details at http://www.gnu.org)"
8
9
10 import re as regex, sys, os, os.path, csv, tempfile, logging, hashlib
11 import urllib2 as wget, decimal, StringIO, MimeWriter, mimetypes, mimetools
12 import cPickle, zlib
13
14
15
16 if __name__ == '__main__':
17
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 _log.info(__version__)
29
30
31 ( CAPS_NONE,
32 CAPS_FIRST,
33 CAPS_ALLCAPS,
34 CAPS_WORDS,
35 CAPS_NAMES,
36 CAPS_FIRST_ONLY
37 ) = range(6)
38
39 default_mail_sender = u'gnumed@gmx.net'
40 default_mail_receiver = u'gnumed-devel@gnu.org'
41 default_mail_server = u'mail.gmx.net'
42
43
44 u_right_double_angle_quote = u'\u00AB'
45 u_registered_trademark = u'\u00AE'
46 u_plus_minus = u'\u00B1'
47 u_left_double_angle_quote = u'\u00BB'
48 u_one_quarter = u'\u00BC'
49 u_one_half = u'\u00BD'
50 u_three_quarters = u'\u00BE'
51 u_ellipsis = u'\u2026'
52 u_left_arrow = u'\u2190'
53 u_right_arrow = u'\u2192'
54 u_corresponds_to = u'\u2258'
55 u_infinity = u'\u221E'
56 u_diameter = u'\u2300'
57 u_checkmark_crossed_out = u'\u237B'
58 u_frowning_face = u'\u2639'
59 u_smiling_face = u'\u263a'
60 u_black_heart = u'\u2665'
61 u_checkmark_thin = u'\u2713'
62 u_checkmark_thick = u'\u2714'
63 u_writing_hand = u'\u270d'
64 u_pencil_1 = u'\u270e'
65 u_pencil_2 = u'\u270f'
66 u_pencil_3 = u'\u2710'
67 u_latin_cross = u'\u271d'
68 u_replacement_character = u'\ufffd'
69
70
71 -def check_for_update(url=None, current_branch=None, current_version=None, consider_latest_branch=False):
72 """Check for new releases at <url>.
73
74 Returns (bool, text).
75 True: new release available
76 False: up to date
77 None: don't know
78 """
79 try:
80 remote_file = wget.urlopen(url)
81 except (wget.URLError, ValueError, OSError):
82 _log.exception("cannot retrieve version file from [%s]", url)
83 return (None, _('Cannot retrieve version information from:\n\n%s') % url)
84
85 _log.debug('retrieving version information from [%s]', url)
86
87 from Gnumed.pycommon import gmCfg2
88 cfg = gmCfg2.gmCfgData()
89 try:
90 cfg.add_stream_source(source = 'gm-versions', stream = remote_file)
91 except (UnicodeDecodeError):
92 remote_file.close()
93 _log.exception("cannot read version file from [%s]", url)
94 return (None, _('Cannot read version information from:\n\n%s') % url)
95
96 remote_file.close()
97
98 latest_branch = cfg.get('latest branch', 'branch', source_order = [('gm-versions', 'return')])
99 latest_release_on_latest_branch = cfg.get('branch %s' % latest_branch, 'latest release', source_order = [('gm-versions', 'return')])
100 latest_release_on_current_branch = cfg.get('branch %s' % current_branch, 'latest release', source_order = [('gm-versions', 'return')])
101
102 cfg.remove_source('gm-versions')
103
104 _log.info('current release: %s', current_version)
105 _log.info('current branch: %s', current_branch)
106 _log.info('latest release on current branch: %s', latest_release_on_current_branch)
107 _log.info('latest branch: %s', latest_branch)
108 _log.info('latest release on latest branch: %s', latest_release_on_latest_branch)
109
110
111 no_release_information_available = (
112 (
113 (latest_release_on_current_branch is None) and
114 (latest_release_on_latest_branch is None)
115 ) or (
116 not consider_latest_branch and
117 (latest_release_on_current_branch is None)
118 )
119 )
120 if no_release_information_available:
121 _log.warning('no release information available')
122 msg = _('There is no version information available from:\n\n%s') % url
123 return (None, msg)
124
125
126 if consider_latest_branch:
127 _log.debug('latest branch taken into account')
128 if current_version >= latest_release_on_latest_branch:
129 _log.debug('up to date: current version >= latest version on latest branch')
130 return (False, None)
131 if latest_release_on_latest_branch is None:
132 if current_version >= latest_release_on_current_branch:
133 _log.debug('up to date: current version >= latest version on current branch and no latest branch available')
134 return (False, None)
135 else:
136 _log.debug('latest branch not taken into account')
137 if current_version >= latest_release_on_current_branch:
138 _log.debug('up to date: current version >= latest version on current branch')
139 return (False, None)
140
141 new_release_on_current_branch_available = (
142 (latest_release_on_current_branch is not None) and
143 (latest_release_on_current_branch > current_version)
144 )
145 _log.info('%snew release on current branch available', bool2str(new_release_on_current_branch_available, '', 'no '))
146
147 new_release_on_latest_branch_available = (
148 (latest_branch is not None)
149 and
150 (
151 (latest_branch > current_branch) or (
152 (latest_branch == current_branch) and
153 (latest_release_on_latest_branch > current_version)
154 )
155 )
156 )
157 _log.info('%snew release on latest branch available', bool2str(new_release_on_latest_branch_available, '', 'no '))
158
159 if not (new_release_on_current_branch_available or new_release_on_latest_branch_available):
160 _log.debug('up to date: no new releases available')
161 return (False, None)
162
163
164 msg = _('A new version of GNUmed is available.\n\n')
165 msg += _(' Your current version: "%s"\n') % current_version
166 if consider_latest_branch:
167 if new_release_on_current_branch_available:
168 msg += u'\n'
169 msg += _(' New version: "%s"') % latest_release_on_current_branch
170 msg += u'\n'
171 msg += _(' - bug fixes only\n')
172 msg += _(' - database fixups may be needed\n')
173 if new_release_on_latest_branch_available:
174 if current_branch != latest_branch:
175 msg += u'\n'
176 msg += _(' New version: "%s"') % latest_release_on_latest_branch
177 msg += u'\n'
178 msg += _(' - bug fixes and new features\n')
179 msg += _(' - database upgrade required\n')
180 else:
181 msg += u'\n'
182 msg += _(' New version: "%s"') % latest_release_on_current_branch
183 msg += u'\n'
184 msg += _(' - bug fixes only\n')
185 msg += _(' - database fixups may be needed\n')
186
187 msg += u'\n\n'
188 msg += _(
189 'Note, however, that this version may not yet\n'
190 'be available *pre-packaged* for your system.'
191 )
192
193 msg += u'\n\n'
194 msg += _('Details are found on <http://wiki.gnumed.de>.\n')
195 msg += u'\n'
196 msg += _('Version information loaded from:\n\n %s') % url
197
198 return (True, msg)
199
201
202 print ".========================================================"
203 print "| Unhandled exception caught !"
204 print "| Type :", t
205 print "| Value:", v
206 print "`========================================================"
207 _log.critical('unhandled exception caught', exc_info = (t,v,tb))
208 sys.__excepthook__(t,v,tb)
209
210
211
212 -def mkdir(directory=None):
213 try:
214 os.makedirs(directory)
215 except OSError, e:
216 if (e.errno == 17) and not os.path.isdir(directory):
217 raise
218 return True
219
220
222 """This class provides the following paths:
223
224 .home_dir
225 .local_base_dir
226 .working_dir
227 .user_config_dir
228 .system_config_dir
229 .system_app_data_dir
230 """
231 - def __init__(self, app_name=None, wx=None):
232 """Setup pathes.
233
234 <app_name> will default to (name of the script - .py)
235 """
236 try:
237 self.already_inited
238 return
239 except AttributeError:
240 pass
241
242 self.init_paths(app_name=app_name, wx=wx)
243 self.already_inited = True
244
245
246
248
249 if wx is None:
250 _log.debug('wxPython not available')
251 _log.debug('detecting paths directly')
252
253 if app_name is None:
254 app_name, ext = os.path.splitext(os.path.basename(sys.argv[0]))
255 _log.info('app name detected as [%s]', app_name)
256 else:
257 _log.info('app name passed in as [%s]', app_name)
258
259
260 self.__home_dir = None
261
262
263
264 self.local_base_dir = os.path.abspath(os.path.dirname(sys.argv[0]))
265
266
267 self.working_dir = os.path.abspath(os.curdir)
268
269
270
271
272 mkdir(os.path.join(self.home_dir, '.%s' % app_name))
273 self.user_config_dir = os.path.join(self.home_dir, '.%s' % app_name)
274
275
276 try:
277 self.system_config_dir = os.path.join('/etc', app_name)
278 except ValueError:
279
280 self.system_config_dir = self.user_config_dir
281
282
283 try:
284 self.system_app_data_dir = os.path.join(sys.prefix, 'share', app_name)
285 except ValueError:
286 self.system_app_data_dir = self.local_base_dir
287
288 self.__log_paths()
289 if wx is None:
290 return True
291
292
293 _log.debug('re-detecting paths with wxPython')
294
295 std_paths = wx.StandardPaths.Get()
296 _log.info('wxPython app name is [%s]', wx.GetApp().GetAppName())
297
298
299 mkdir(os.path.join(std_paths.GetUserConfigDir(), '.%s' % app_name))
300 self.user_config_dir = os.path.join(std_paths.GetUserConfigDir(), '.%s' % app_name)
301
302
303 try:
304 tmp = std_paths.GetConfigDir()
305 if not tmp.endswith(app_name):
306 tmp = os.path.join(tmp, app_name)
307 self.system_config_dir = tmp
308 except ValueError:
309
310 pass
311
312
313
314
315 if 'wxMSW' in wx.PlatformInfo:
316 _log.warning('this platform (wxMSW) sometimes returns a broken value for the system-wide application data dir')
317 else:
318 try:
319 self.system_app_data_dir = std_paths.GetDataDir()
320 except ValueError:
321 pass
322
323 self.__log_paths()
324 return True
325
327 _log.debug('sys.argv[0]: %s', sys.argv[0])
328 _log.debug('local application base dir: %s', self.local_base_dir)
329 _log.debug('current working dir: %s', self.working_dir)
330
331 _log.debug('user home dir: %s', self.home_dir)
332 _log.debug('user-specific config dir: %s', self.user_config_dir)
333 _log.debug('system-wide config dir: %s', self.system_config_dir)
334 _log.debug('system-wide application data dir: %s', self.system_app_data_dir)
335
336
337
339 if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)):
340 msg = '[%s:user_config_dir]: invalid path [%s]' % (self.__class__.__name__, path)
341 _log.error(msg)
342 raise ValueError(msg)
343 self.__user_config_dir = path
344
346 return self.__user_config_dir
347
348 user_config_dir = property(_get_user_config_dir, _set_user_config_dir)
349
351 if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)):
352 msg = '[%s:system_config_dir]: invalid path [%s]' % (self.__class__.__name__, path)
353 _log.error(msg)
354 raise ValueError(msg)
355 self.__system_config_dir = path
356
358 return self.__system_config_dir
359
360 system_config_dir = property(_get_system_config_dir, _set_system_config_dir)
361
363 if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)):
364 msg = '[%s:system_app_data_dir]: invalid path [%s]' % (self.__class__.__name__, path)
365 _log.error(msg)
366 raise ValueError(msg)
367 self.__system_app_data_dir = path
368
370 return self.__system_app_data_dir
371
372 system_app_data_dir = property(_get_system_app_data_dir, _set_system_app_data_dir)
373
375 raise ArgumentError('invalid to set home dir')
376
378 if self.__home_dir is not None:
379 return self.__home_dir
380
381 tmp = os.path.expanduser('~')
382 if tmp == '~':
383 _log.error('this platform does not expand ~ properly')
384 try:
385 tmp = os.environ['USERPROFILE']
386 except KeyError:
387 _log.error('cannot access $USERPROFILE in environment')
388
389 if not (
390 os.access(tmp, os.R_OK)
391 and
392 os.access(tmp, os.X_OK)
393 and
394 os.access(tmp, os.W_OK)
395 ):
396 msg = '[%s:home_dir]: invalid path [%s]' % (self.__class__.__name__, tmp)
397 _log.error(msg)
398 raise ValueError(msg)
399
400 self.__home_dir = tmp
401 return self.__home_dir
402
403 home_dir = property(_get_home_dir, _set_home_dir)
404
405 -def send_mail(sender=None, receiver=None, message=None, server=None, auth=None, debug=False, subject=None, encoding='quoted-printable', attachments=None):
406
407 if message is None:
408 return False
409
410 message = message.lstrip().lstrip('\r\n').lstrip()
411
412 if sender is None:
413 sender = default_mail_sender
414
415 if receiver is None:
416 receiver = [default_mail_receiver]
417
418 if server is None:
419 server = default_mail_server
420
421 if subject is None:
422 subject = u'gmTools.py: send_mail() test'
423
424 msg = StringIO.StringIO()
425 writer = MimeWriter.MimeWriter(msg)
426 writer.addheader('To', u', '.join(receiver))
427 writer.addheader('From', sender)
428 writer.addheader('Subject', subject[:50].replace('\r', '/').replace('\n', '/'))
429 writer.addheader('MIME-Version', '1.0')
430
431 writer.startmultipartbody('mixed')
432
433
434 part = writer.nextpart()
435 body = part.startbody('text/plain')
436 part.flushheaders()
437 body.write(message.encode(encoding))
438
439
440 if attachments is not None:
441 for a in attachments:
442 filename = os.path.basename(a[0])
443 try:
444 mtype = a[1]
445 encoding = a[2]
446 except IndexError:
447 mtype, encoding = mimetypes.guess_type(a[0])
448 if mtype is None:
449 mtype = 'application/octet-stream'
450 encoding = 'base64'
451 elif mtype == 'text/plain':
452 encoding = 'quoted-printable'
453 else:
454 encoding = 'base64'
455
456 part = writer.nextpart()
457 part.addheader('Content-Transfer-Encoding', encoding)
458 body = part.startbody("%s; name=%s" % (mtype, filename))
459 mimetools.encode(open(a[0], 'rb'), body, encoding)
460
461 writer.lastpart()
462
463 import smtplib
464 session = smtplib.SMTP(server)
465 session.set_debuglevel(debug)
466 if auth is not None:
467 session.login(auth['user'], auth['password'])
468 refused = session.sendmail(sender, receiver, msg.getvalue())
469 session.quit()
470 msg.close()
471 if len(refused) != 0:
472 _log.error("refused recipients: %s" % refused)
473 return False
474
475 return True
476
477 -def send_mail_old(sender=None, receiver=None, message=None, server=None, auth=None, debug=False, subject=None, encoding='latin1'):
478 """Send an E-Mail.
479
480 <debug>: see smtplib.set_debuglevel()
481 <auth>: {'user': ..., 'password': ...}
482 <receiver>: a list of email addresses
483 """
484 if message is None:
485 return False
486 message = message.lstrip().lstrip('\r\n').lstrip()
487
488 if sender is None:
489 sender = default_mail_sender
490
491 if receiver is None:
492 receiver = [default_mail_receiver]
493
494 if server is None:
495 server = default_mail_server
496
497 if subject is None:
498 subject = u'gmTools.py: send_mail() test'
499
500 body = u"""From: %s
501 To: %s
502 Subject: %s
503
504 %s
505 """ % (sender, u', '.join(receiver), subject[:50].replace('\r', '/').replace('\n', '/'), message)
506
507 import smtplib
508 session = smtplib.SMTP(server)
509 session.set_debuglevel(debug)
510 if auth is not None:
511 session.login(auth['user'], auth['password'])
512 refused = session.sendmail(sender, receiver, body.encode(encoding))
513 session.quit()
514 if len(refused) != 0:
515 _log.error("refused recipients: %s" % refused)
516 return False
517
518 return True
519
520
521
522 -def file2md5(filename=None, return_hex=True):
523 blocksize = 2**10 * 128
524 _log.debug('md5(%s): <%s> byte blocks', filename, blocksize)
525
526 f = open(filename, 'rb')
527
528 md5 = hashlib.md5()
529 while True:
530 data = f.read(blocksize)
531 if not data:
532 break
533 md5.update(data)
534
535 _log.debug('md5(%s): %s', filename, md5.hexdigest())
536
537 if return_hex:
538 return md5.hexdigest()
539 return md5.digest()
540
542 for line in unicode_csv_data:
543 yield line.encode(encoding)
544
545
546
547
548
550
551 try:
552 is_dict_reader = kwargs['dict']
553 del kwargs['dict']
554 if is_dict_reader is not True:
555 raise KeyError
556 csv_reader = csv.DictReader(unicode2charset_encoder(unicode_csv_data), dialect=dialect, **kwargs)
557 except KeyError:
558 is_dict_reader = False
559 csv_reader = csv.reader(unicode2charset_encoder(unicode_csv_data), dialect=dialect, **kwargs)
560
561 for row in csv_reader:
562
563 if is_dict_reader:
564 for key in row.keys():
565 row[key] = unicode(row[key], encoding)
566 yield row
567 else:
568 yield [ unicode(cell, encoding) for cell in row ]
569
570
572 """This introduces a race condition between the file.close() and
573 actually using the filename.
574
575 The file will not exist after calling this function.
576 """
577 if tmp_dir is not None:
578 if (
579 not os.access(tmp_dir, os.F_OK)
580 or
581 not os.access(tmp_dir, os.X_OK | os.W_OK)
582 ):
583 _log.info('cannot find temporary dir [%s], using system default', tmp_dir)
584 tmp_dir = None
585
586 kwargs = {'dir': tmp_dir}
587
588 if prefix is None:
589 kwargs['prefix'] = 'gnumed-'
590 else:
591 kwargs['prefix'] = prefix
592
593 if suffix in [None, u'']:
594 kwargs['suffix'] = '.tmp'
595 else:
596 if not suffix.startswith('.'):
597 suffix = '.' + suffix
598 kwargs['suffix'] = suffix
599
600 f = tempfile.NamedTemporaryFile(**kwargs)
601 filename = f.name
602 f.close()
603
604 return filename
605
607 """Import a module from any location."""
608
609 remove_path = always_remove_path or False
610 if module_path not in sys.path:
611 _log.info('appending to sys.path: [%s]' % module_path)
612 sys.path.append(module_path)
613 remove_path = True
614
615 _log.debug('will remove import path: %s', remove_path)
616
617 if module_name.endswith('.py'):
618 module_name = module_name[:-3]
619
620 try:
621 module = __import__(module_name)
622 except StandardError:
623 _log.exception('cannot __import__() module [%s] from [%s]' % (module_name, module_path))
624 while module_path in sys.path:
625 sys.path.remove(module_path)
626 raise
627
628 _log.info('imported module [%s] as [%s]' % (module_name, module))
629 if remove_path:
630 while module_path in sys.path:
631 sys.path.remove(module_path)
632
633 return module
634
635
636
637 _kB = 1024
638 _MB = 1024 * _kB
639 _GB = 1024 * _MB
640 _TB = 1024 * _GB
641 _PB = 1024 * _TB
642
644 if size == 1:
645 return template % _('1 Byte')
646 if size < 10 * _kB:
647 return template % _('%s Bytes') % size
648 if size < _MB:
649 return template % u'%.1f kB' % (float(size) / _kB)
650 if size < _GB:
651 return template % u'%.1f MB' % (float(size) / _MB)
652 if size < _TB:
653 return template % u'%.1f GB' % (float(size) / _GB)
654 if size < _PB:
655 return template % u'%.1f TB' % (float(size) / _TB)
656 return template % u'%.1f PB' % (float(size) / _PB)
657
658 -def bool2subst(boolean=None, true_return=True, false_return=False, none_return=None):
659 if boolean is None:
660 return none_return
661 if boolean is True:
662 return true_return
663 if boolean is False:
664 return false_return
665 raise ValueError('bool2subst(): <boolean> arg must be either of True, False, None')
666
667 -def bool2str(boolean=None, true_str='True', false_str='False'):
668 return bool2subst (
669 boolean = bool(boolean),
670 true_return = true_str,
671 false_return = false_str
672 )
673
674 -def none_if(value=None, none_equivalent=None):
675 """Modelled after the SQL NULLIF function."""
676 if value == none_equivalent:
677 return None
678 return value
679
680 -def coalesce(initial=None, instead=None, template_initial=None, template_instead=None, none_equivalents=None):
681 """Modelled after the SQL coalesce function.
682
683 To be used to simplify constructs like:
684
685 if initial is None (or in none_equivalents):
686 real_value = (template_instead % instead) or instead
687 else:
688 real_value = (template_initial % initial) or initial
689 print real_value
690
691 @param initial: the value to be tested for <None>
692 @type initial: any Python type, must have a __str__ method if template_initial is not None
693 @param instead: the value to be returned if <initial> is None
694 @type instead: any Python type, must have a __str__ method if template_instead is not None
695 @param template_initial: if <initial> is returned replace the value into this template, must contain one <%s>
696 @type template_initial: string or None
697 @param template_instead: if <instead> is returned replace the value into this template, must contain one <%s>
698 @type template_instead: string or None
699
700 Ideas:
701 - list of insteads: initial, [instead, template], [instead, template], [instead, template], template_initial, ...
702 """
703 if none_equivalents is None:
704 none_equivalents = [None]
705
706 if initial in none_equivalents:
707
708 if template_instead is None:
709 return instead
710
711 return template_instead % instead
712
713 if template_initial is None:
714 return initial
715
716 try:
717 return template_initial % initial
718 except TypeError:
719 return template_initial
720
722 val = match_obj.group(0).lower()
723 if val in ['von', 'van', 'de', 'la', 'l', 'der', 'den']:
724 return val
725 buf = list(val)
726 buf[0] = buf[0].upper()
727 for part in ['mac', 'mc', 'de', 'la']:
728 if len(val) > len(part) and val[:len(part)] == part:
729 buf[len(part)] = buf[len(part)].upper()
730 return ''.join(buf)
731
733 """Capitalize the first character but leave the rest alone.
734
735 Note that we must be careful about the locale, this may
736 have issues ! However, for UTF strings it should just work.
737 """
738 if (mode is None) or (mode == CAPS_NONE):
739 return text
740
741 if mode == CAPS_FIRST:
742 if len(text) == 1:
743 return text[0].upper()
744 return text[0].upper() + text[1:]
745
746 if mode == CAPS_ALLCAPS:
747 return text.upper()
748
749 if mode == CAPS_FIRST_ONLY:
750 if len(text) == 1:
751 return text[0].upper()
752 return text[0].upper() + text[1:].lower()
753
754 if mode == CAPS_WORDS:
755 return regex.sub(ur'(\w)(\w+)', lambda x: x.group(1).upper() + x.group(2).lower(), text)
756
757 if mode == CAPS_NAMES:
758
759 return capitalize(text=text, mode=CAPS_FIRST)
760
761 print "ERROR: invalid capitalization mode: [%s], leaving input as is" % mode
762 return text
763
785
786 -def wrap(text=None, width=None, initial_indent=u'', subsequent_indent=u'', eol=u'\n'):
787 """A word-wrap function that preserves existing line breaks
788 and most spaces in the text. Expects that existing line
789 breaks are posix newlines (\n).
790 """
791 wrapped = initial_indent + reduce (
792 lambda line, word, width=width: '%s%s%s' % (
793 line,
794 ' \n'[(len(line) - line.rfind('\n') - 1 + len(word.split('\n',1)[0]) >= width)],
795 word
796 ),
797 text.split(' ')
798 )
799
800 if subsequent_indent != u'':
801 wrapped = (u'\n%s' % subsequent_indent).join(wrapped.split('\n'))
802
803 if eol != u'\n':
804 wrapped = wrapped.replace('\n', eol)
805
806 return wrapped
807
808 -def unwrap(text=None, max_length=None, strip_whitespace=True, remove_empty_lines=True, line_separator = u' // '):
809
810 text = text.replace(u'\r', u'')
811 lines = text.split(u'\n')
812 text = u''
813 for line in lines:
814
815 if strip_whitespace:
816 line = line.strip().strip(u'\t').strip()
817
818 if remove_empty_lines:
819 if line == u'':
820 continue
821
822 text += (u'%s%s' % (line, line_separator))
823
824 text = text.rstrip(line_separator)
825
826 if max_length is not None:
827 text = text[:max_length]
828
829 text = text.rstrip(line_separator)
830
831 return text
832
834 """check for special latex-characters and transform them"""
835
836 text = text.replace(u'\\', u'$\\backslash$')
837 text = text.replace(u'{', u'\\{')
838 text = text.replace(u'}', u'\\}')
839 text = text.replace(u'%', u'\\%')
840 text = text.replace(u'&', u'\\&')
841 text = text.replace(u'#', u'\\#')
842 text = text.replace(u'$', u'\\$')
843 text = text.replace(u'_', u'\\_')
844
845 text = text.replace(u'^', u'\\verb#^#')
846 text = text.replace('~','\\verb#~#')
847
848 return text
849
850
851
852
853 __icon_serpent = \
854 """x\xdae\x8f\xb1\x0e\x83 \x10\x86w\x9f\xe2\x92\x1blb\xf2\x07\x96\xeaH:0\xd6\
855 \xc1\x85\xd5\x98N5\xa5\xef?\xf5N\xd0\x8a\xdcA\xc2\xf7qw\x84\xdb\xfa\xb5\xcd\
856 \xd4\xda;\xc9\x1a\xc8\xb6\xcd<\xb5\xa0\x85\x1e\xeb\xbc\xbc7b!\xf6\xdeHl\x1c\
857 \x94\x073\xec<*\xf7\xbe\xf7\x99\x9d\xb21~\xe7.\xf5\x1f\x1c\xd3\xbdVlL\xc2\
858 \xcf\xf8ye\xd0\x00\x90\x0etH \x84\x80B\xaa\x8a\x88\x85\xc4(U\x9d$\xfeR;\xc5J\
859 \xa6\x01\xbbt9\xceR\xc8\x81e_$\x98\xb9\x9c\xa9\x8d,y\xa9t\xc8\xcf\x152\xe0x\
860 \xe9$\xf5\x07\x95\x0cD\x95t:\xb1\x92\xae\x9cI\xa8~\x84\x1f\xe0\xa3ec"""
861
863
864 paths = gmPaths(app_name = u'gnumed', wx = wx)
865
866 candidates = [
867 os.path.join(paths.system_app_data_dir, 'bitmaps', 'gm_icon-serpent_and_gnu.png'),
868 os.path.join(paths.local_base_dir, 'bitmaps', 'gm_icon-serpent_and_gnu.png'),
869 os.path.join(paths.system_app_data_dir, 'bitmaps', 'serpent.png'),
870 os.path.join(paths.local_base_dir, 'bitmaps', 'serpent.png')
871 ]
872
873 found_as = None
874 for candidate in candidates:
875 try:
876 open(candidate, 'r').close()
877 found_as = candidate
878 break
879 except IOError:
880 _log.debug('icon not found in [%s]', candidate)
881
882 if found_as is None:
883 _log.warning('no icon file found, falling back to builtin (ugly) icon')
884 icon_bmp_data = wx.BitmapFromXPMData(cPickle.loads(zlib.decompress(__icon_serpent)))
885 icon.CopyFromBitmap(icon_bmp_data)
886 else:
887 _log.debug('icon found in [%s]', found_as)
888 icon = wx.EmptyIcon()
889 icon.LoadFile(found_as, wx.BITMAP_TYPE_ANY)
890
891 return icon
892
893
894
895 if __name__ == '__main__':
896
897 if len(sys.argv) < 2:
898 sys.exit()
899
900 if sys.argv[1] != 'test':
901 sys.exit()
902
903
959
961 print 'testing coalesce()'
962 print "------------------"
963 tests = [
964 [None, 'something other than <None>', None, None, 'something other than <None>'],
965 ['Captain', 'Mr.', '%s.'[:4], 'Mr.', 'Capt.'],
966 ['value to test', 'test 3 failed', 'template with "%s" included', None, 'template with "value to test" included'],
967 ['value to test', 'test 4 failed', 'template with value not included', None, 'template with value not included'],
968 [None, 'initial value was None', 'template_initial: %s', None, 'initial value was None'],
969 [None, 'initial value was None', 'template_initial: %%(abc)s', None, 'initial value was None']
970 ]
971 passed = True
972 for test in tests:
973 result = coalesce (
974 initial = test[0],
975 instead = test[1],
976 template_initial = test[2],
977 template_instead = test[3]
978 )
979 if result != test[4]:
980 print "ERROR"
981 print "coalesce: (%s, %s, %s, %s)" % (test[0], test[1], test[2], test[3])
982 print "expected:", test[4]
983 print "received:", result
984 passed = False
985
986 if passed:
987 print "passed"
988 else:
989 print "failed"
990 return passed
991
993 print 'testing capitalize() ...'
994 success = True
995 pairs = [
996
997 [u'Boot', u'Boot', CAPS_FIRST_ONLY],
998 [u'boot', u'Boot', CAPS_FIRST_ONLY],
999 [u'booT', u'Boot', CAPS_FIRST_ONLY],
1000 [u'BoOt', u'Boot', CAPS_FIRST_ONLY],
1001 [u'boots-Schau', u'Boots-Schau', CAPS_WORDS],
1002 [u'boots-sChau', u'Boots-Schau', CAPS_WORDS],
1003 [u'boot camp', u'Boot Camp', CAPS_WORDS],
1004 [u'fahrner-Kampe', u'Fahrner-Kampe', CAPS_NAMES],
1005 [u'häkkönen', u'Häkkönen', CAPS_NAMES],
1006 [u'McBurney', u'McBurney', CAPS_NAMES],
1007 [u'mcBurney', u'McBurney', CAPS_NAMES],
1008 [u'blumberg', u'Blumberg', CAPS_NAMES],
1009 [u'roVsing', u'RoVsing', CAPS_NAMES],
1010 [u'Özdemir', u'Özdemir', CAPS_NAMES],
1011 [u'özdemir', u'Özdemir', CAPS_NAMES],
1012 ]
1013 for pair in pairs:
1014 result = capitalize(pair[0], pair[2])
1015 if result != pair[1]:
1016 success = False
1017 print 'ERROR (caps mode %s): "%s" -> "%s", expected "%s"' % (pair[2], pair[0], result, pair[1])
1018
1019 if success:
1020 print "... SUCCESS"
1021
1022 return success
1023
1025 print "testing import_module_from_directory()"
1026 path = sys.argv[1]
1027 name = sys.argv[2]
1028 try:
1029 mod = import_module_from_directory(module_path = path, module_name = name)
1030 except:
1031 print "module import failed, see log"
1032 return False
1033
1034 print "module import succeeded", mod
1035 print dir(mod)
1036 return True
1037
1039 print "testing mkdir()"
1040 mkdir(sys.argv[1])
1041
1043 msg = u"""
1044 To: %s
1045 From: %s
1046 Subject: gmTools test suite mail
1047
1048 This is a test mail from the gmTools.py module.
1049 """ % (default_mail_receiver, default_mail_sender)
1050 print "mail sending succeeded:", send_mail (
1051 receiver = [default_mail_receiver, u'karsten.hilbert@gmx.net'],
1052 message = msg,
1053 auth = {'user': default_mail_sender, 'password': u'gnumed-at-gmx-net'},
1054 debug = True,
1055 attachments = [sys.argv[0]]
1056 )
1057
1059 print "testing gmPaths()"
1060 print "-----------------"
1061 paths = gmPaths(wx=None, app_name='gnumed')
1062 print "user config dir:", paths.user_config_dir
1063 print "system config dir:", paths.system_config_dir
1064 print "local base dir:", paths.local_base_dir
1065 print "system app data dir:", paths.system_app_data_dir
1066 print "working directory :", paths.working_dir
1067
1069 print "testing none_if()"
1070 print "-----------------"
1071 tests = [
1072 [None, None, None],
1073 ['a', 'a', None],
1074 ['a', 'b', 'a'],
1075 ['a', None, 'a'],
1076 [None, 'a', None],
1077 [1, 1, None],
1078 [1, 2, 1],
1079 [1, None, 1],
1080 [None, 1, None]
1081 ]
1082
1083 for test in tests:
1084 if none_if(value = test[0], none_equivalent = test[1]) != test[2]:
1085 print 'ERROR: none_if(%s) returned [%s], expected [%s]' % (test[0], none_if(test[0], test[1]), test[2])
1086
1087 return True
1088
1090 tests = [
1091 [True, 'Yes', 'Yes', 'Yes'],
1092 [False, 'OK', 'not OK', 'not OK']
1093 ]
1094 for test in tests:
1095 if bool2str(test[0], test[1], test[2]) != test[3]:
1096 print 'ERROR: bool2str(%s, %s, %s) returned [%s], expected [%s]' % (test[0], test[1], test[2], bool2str(test[0], test[1], test[2]), test[3])
1097
1098 return True
1099
1101
1102 print bool2subst(True, 'True', 'False', 'is None')
1103 print bool2subst(False, 'True', 'False', 'is None')
1104 print bool2subst(None, 'True', 'False', 'is None')
1105
1112
1114 print "testing size2str()"
1115 print "------------------"
1116 tests = [0, 1, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000, 1000000000000, 10000000000000]
1117 for test in tests:
1118 print size2str(test)
1119
1121
1122 test = """
1123 second line\n
1124 3rd starts with tab \n
1125 4th with a space \n
1126
1127 6th
1128
1129 """
1130 print unwrap(text = test, max_length = 25)
1131
1133 test = 'line 1\nline 2\nline 3'
1134
1135 print "wrap 5-6-7 initial 0, subsequent 0"
1136 print wrap(test, 5)
1137 print
1138 print wrap(test, 6)
1139 print
1140 print wrap(test, 7)
1141 print "-------"
1142 raw_input()
1143 print "wrap 5 initial 1-1-3, subsequent 1-3-1"
1144 print wrap(test, 5, u' ', u' ')
1145 print
1146 print wrap(test, 5, u' ', u' ')
1147 print
1148 print wrap(test, 5, u' ', u' ')
1149 print "-------"
1150 raw_input()
1151 print "wrap 6 initial 1-1-3, subsequent 1-3-1"
1152 print wrap(test, 6, u' ', u' ')
1153 print
1154 print wrap(test, 6, u' ', u' ')
1155 print
1156 print wrap(test, 6, u' ', u' ')
1157 print "-------"
1158 raw_input()
1159 print "wrap 7 initial 1-1-3, subsequent 1-3-1"
1160 print wrap(test, 7, u' ', u' ')
1161 print
1162 print wrap(test, 7, u' ', u' ')
1163 print
1164 print wrap(test, 7, u' ', u' ')
1165
1167
1168 test_data = [
1169 ('http://www.gnumed.de/downloads/gnumed-versions.txt', None, None, False),
1170 ('file:///home/ncq/gm-versions.txt', None, None, False),
1171 ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.1', False),
1172 ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.1', True),
1173 ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.5', True)
1174 ]
1175
1176 for test in test_data:
1177 print "arguments:", test
1178 found, msg = check_for_update(test[0], test[1], test[2], test[3])
1179 print msg
1180
1181 return
1182
1184 print '%s: %s' % (sys.argv[2], file2md5(sys.argv[2]))
1185
1186
1187
1188
1189
1190
1191
1192 test_gmPaths()
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204