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
851
852
853
854 __icon_serpent = \
855 """x\xdae\x8f\xb1\x0e\x83 \x10\x86w\x9f\xe2\x92\x1blb\xf2\x07\x96\xeaH:0\xd6\
856 \xc1\x85\xd5\x98N5\xa5\xef?\xf5N\xd0\x8a\xdcA\xc2\xf7qw\x84\xdb\xfa\xb5\xcd\
857 \xd4\xda;\xc9\x1a\xc8\xb6\xcd<\xb5\xa0\x85\x1e\xeb\xbc\xbc7b!\xf6\xdeHl\x1c\
858 \x94\x073\xec<*\xf7\xbe\xf7\x99\x9d\xb21~\xe7.\xf5\x1f\x1c\xd3\xbdVlL\xc2\
859 \xcf\xf8ye\xd0\x00\x90\x0etH \x84\x80B\xaa\x8a\x88\x85\xc4(U\x9d$\xfeR;\xc5J\
860 \xa6\x01\xbbt9\xceR\xc8\x81e_$\x98\xb9\x9c\xa9\x8d,y\xa9t\xc8\xcf\x152\xe0x\
861 \xe9$\xf5\x07\x95\x0cD\x95t:\xb1\x92\xae\x9cI\xa8~\x84\x1f\xe0\xa3ec"""
862
864
865 paths = gmPaths(app_name = u'gnumed', wx = wx)
866
867 candidates = [
868 os.path.join(paths.system_app_data_dir, 'bitmaps', 'gm_icon-serpent_and_gnu.png'),
869 os.path.join(paths.local_base_dir, 'bitmaps', 'gm_icon-serpent_and_gnu.png'),
870 os.path.join(paths.system_app_data_dir, 'bitmaps', 'serpent.png'),
871 os.path.join(paths.local_base_dir, 'bitmaps', 'serpent.png')
872 ]
873
874 found_as = None
875 for candidate in candidates:
876 try:
877 open(candidate, 'r').close()
878 found_as = candidate
879 break
880 except IOError:
881 _log.debug('icon not found in [%s]', candidate)
882
883 if found_as is None:
884 _log.warning('no icon file found, falling back to builtin (ugly) icon')
885 icon_bmp_data = wx.BitmapFromXPMData(cPickle.loads(zlib.decompress(__icon_serpent)))
886 icon.CopyFromBitmap(icon_bmp_data)
887 else:
888 _log.debug('icon found in [%s]', found_as)
889 icon = wx.EmptyIcon()
890 try:
891 icon.LoadFile(found_as, wx.BITMAP_TYPE_ANY)
892 except AttributeError:
893 _log.exception(u"this platform doesn't support wx.Icon().LoadFile()")
894
895 return icon
896
897
898
899 if __name__ == '__main__':
900
901 if len(sys.argv) < 2:
902 sys.exit()
903
904 if sys.argv[1] != 'test':
905 sys.exit()
906
907
963
965 print 'testing coalesce()'
966 print "------------------"
967 tests = [
968 [None, 'something other than <None>', None, None, 'something other than <None>'],
969 ['Captain', 'Mr.', '%s.'[:4], 'Mr.', 'Capt.'],
970 ['value to test', 'test 3 failed', 'template with "%s" included', None, 'template with "value to test" included'],
971 ['value to test', 'test 4 failed', 'template with value not included', None, 'template with value not included'],
972 [None, 'initial value was None', 'template_initial: %s', None, 'initial value was None'],
973 [None, 'initial value was None', 'template_initial: %%(abc)s', None, 'initial value was None']
974 ]
975 passed = True
976 for test in tests:
977 result = coalesce (
978 initial = test[0],
979 instead = test[1],
980 template_initial = test[2],
981 template_instead = test[3]
982 )
983 if result != test[4]:
984 print "ERROR"
985 print "coalesce: (%s, %s, %s, %s)" % (test[0], test[1], test[2], test[3])
986 print "expected:", test[4]
987 print "received:", result
988 passed = False
989
990 if passed:
991 print "passed"
992 else:
993 print "failed"
994 return passed
995
997 print 'testing capitalize() ...'
998 success = True
999 pairs = [
1000
1001 [u'Boot', u'Boot', CAPS_FIRST_ONLY],
1002 [u'boot', u'Boot', CAPS_FIRST_ONLY],
1003 [u'booT', u'Boot', CAPS_FIRST_ONLY],
1004 [u'BoOt', u'Boot', CAPS_FIRST_ONLY],
1005 [u'boots-Schau', u'Boots-Schau', CAPS_WORDS],
1006 [u'boots-sChau', u'Boots-Schau', CAPS_WORDS],
1007 [u'boot camp', u'Boot Camp', CAPS_WORDS],
1008 [u'fahrner-Kampe', u'Fahrner-Kampe', CAPS_NAMES],
1009 [u'häkkönen', u'Häkkönen', CAPS_NAMES],
1010 [u'McBurney', u'McBurney', CAPS_NAMES],
1011 [u'mcBurney', u'McBurney', CAPS_NAMES],
1012 [u'blumberg', u'Blumberg', CAPS_NAMES],
1013 [u'roVsing', u'RoVsing', CAPS_NAMES],
1014 [u'Özdemir', u'Özdemir', CAPS_NAMES],
1015 [u'özdemir', u'Özdemir', CAPS_NAMES],
1016 ]
1017 for pair in pairs:
1018 result = capitalize(pair[0], pair[2])
1019 if result != pair[1]:
1020 success = False
1021 print 'ERROR (caps mode %s): "%s" -> "%s", expected "%s"' % (pair[2], pair[0], result, pair[1])
1022
1023 if success:
1024 print "... SUCCESS"
1025
1026 return success
1027
1029 print "testing import_module_from_directory()"
1030 path = sys.argv[1]
1031 name = sys.argv[2]
1032 try:
1033 mod = import_module_from_directory(module_path = path, module_name = name)
1034 except:
1035 print "module import failed, see log"
1036 return False
1037
1038 print "module import succeeded", mod
1039 print dir(mod)
1040 return True
1041
1043 print "testing mkdir()"
1044 mkdir(sys.argv[1])
1045
1047 msg = u"""
1048 To: %s
1049 From: %s
1050 Subject: gmTools test suite mail
1051
1052 This is a test mail from the gmTools.py module.
1053 """ % (default_mail_receiver, default_mail_sender)
1054 print "mail sending succeeded:", send_mail (
1055 receiver = [default_mail_receiver, u'karsten.hilbert@gmx.net'],
1056 message = msg,
1057 auth = {'user': default_mail_sender, 'password': u'gnumed-at-gmx-net'},
1058 debug = True,
1059 attachments = [sys.argv[0]]
1060 )
1061
1063 print "testing gmPaths()"
1064 print "-----------------"
1065 paths = gmPaths(wx=None, app_name='gnumed')
1066 print "user config dir:", paths.user_config_dir
1067 print "system config dir:", paths.system_config_dir
1068 print "local base dir:", paths.local_base_dir
1069 print "system app data dir:", paths.system_app_data_dir
1070 print "working directory :", paths.working_dir
1071
1073 print "testing none_if()"
1074 print "-----------------"
1075 tests = [
1076 [None, None, None],
1077 ['a', 'a', None],
1078 ['a', 'b', 'a'],
1079 ['a', None, 'a'],
1080 [None, 'a', None],
1081 [1, 1, None],
1082 [1, 2, 1],
1083 [1, None, 1],
1084 [None, 1, None]
1085 ]
1086
1087 for test in tests:
1088 if none_if(value = test[0], none_equivalent = test[1]) != test[2]:
1089 print 'ERROR: none_if(%s) returned [%s], expected [%s]' % (test[0], none_if(test[0], test[1]), test[2])
1090
1091 return True
1092
1094 tests = [
1095 [True, 'Yes', 'Yes', 'Yes'],
1096 [False, 'OK', 'not OK', 'not OK']
1097 ]
1098 for test in tests:
1099 if bool2str(test[0], test[1], test[2]) != test[3]:
1100 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])
1101
1102 return True
1103
1105
1106 print bool2subst(True, 'True', 'False', 'is None')
1107 print bool2subst(False, 'True', 'False', 'is None')
1108 print bool2subst(None, 'True', 'False', 'is None')
1109
1116
1118 print "testing size2str()"
1119 print "------------------"
1120 tests = [0, 1, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000, 1000000000000, 10000000000000]
1121 for test in tests:
1122 print size2str(test)
1123
1125
1126 test = """
1127 second line\n
1128 3rd starts with tab \n
1129 4th with a space \n
1130
1131 6th
1132
1133 """
1134 print unwrap(text = test, max_length = 25)
1135
1137 test = 'line 1\nline 2\nline 3'
1138
1139 print "wrap 5-6-7 initial 0, subsequent 0"
1140 print wrap(test, 5)
1141 print
1142 print wrap(test, 6)
1143 print
1144 print wrap(test, 7)
1145 print "-------"
1146 raw_input()
1147 print "wrap 5 initial 1-1-3, subsequent 1-3-1"
1148 print wrap(test, 5, u' ', u' ')
1149 print
1150 print wrap(test, 5, u' ', u' ')
1151 print
1152 print wrap(test, 5, u' ', u' ')
1153 print "-------"
1154 raw_input()
1155 print "wrap 6 initial 1-1-3, subsequent 1-3-1"
1156 print wrap(test, 6, u' ', u' ')
1157 print
1158 print wrap(test, 6, u' ', u' ')
1159 print
1160 print wrap(test, 6, u' ', u' ')
1161 print "-------"
1162 raw_input()
1163 print "wrap 7 initial 1-1-3, subsequent 1-3-1"
1164 print wrap(test, 7, u' ', u' ')
1165 print
1166 print wrap(test, 7, u' ', u' ')
1167 print
1168 print wrap(test, 7, u' ', u' ')
1169
1171
1172 test_data = [
1173 ('http://www.gnumed.de/downloads/gnumed-versions.txt', None, None, False),
1174 ('file:///home/ncq/gm-versions.txt', None, None, False),
1175 ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.1', False),
1176 ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.1', True),
1177 ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.5', True)
1178 ]
1179
1180 for test in test_data:
1181 print "arguments:", test
1182 found, msg = check_for_update(test[0], test[1], test[2], test[3])
1183 print msg
1184
1185 return
1186
1188 print '%s: %s' % (sys.argv[2], file2md5(sys.argv[2]))
1189
1190
1191
1192
1193
1194
1195
1196 test_gmPaths()
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208