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