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 ValueError('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
550 default_csv_reader_rest_key = u'list_of_values_of_unknown_fields'
551
553
554
555 try:
556 is_dict_reader = kwargs['dict']
557 del kwargs['dict']
558 if is_dict_reader is not True:
559 raise KeyError
560 kwargs['restkey'] = default_csv_reader_rest_key
561 csv_reader = csv.DictReader(unicode2charset_encoder(unicode_csv_data), dialect=dialect, **kwargs)
562 except KeyError:
563 is_dict_reader = False
564 csv_reader = csv.reader(unicode2charset_encoder(unicode_csv_data), dialect=dialect, **kwargs)
565
566 for row in csv_reader:
567
568 if is_dict_reader:
569 for key in row.keys():
570 if key == default_csv_reader_rest_key:
571 old_data = row[key]
572 new_data = []
573 for val in old_data:
574 new_data.append(unicode(val, encoding))
575 row[key] = new_data
576 if default_csv_reader_rest_key not in csv_reader.fieldnames:
577 csv_reader.fieldnames.append(default_csv_reader_rest_key)
578 else:
579 row[key] = unicode(row[key], encoding)
580 yield row
581 else:
582 yield [ unicode(cell, encoding) for cell in row ]
583
584
586 """This introduces a race condition between the file.close() and
587 actually using the filename.
588
589 The file will not exist after calling this function.
590 """
591 if tmp_dir is not None:
592 if (
593 not os.access(tmp_dir, os.F_OK)
594 or
595 not os.access(tmp_dir, os.X_OK | os.W_OK)
596 ):
597 _log.info('cannot find temporary dir [%s], using system default', tmp_dir)
598 tmp_dir = None
599
600 kwargs = {'dir': tmp_dir}
601
602 if prefix is None:
603 kwargs['prefix'] = 'gnumed-'
604 else:
605 kwargs['prefix'] = prefix
606
607 if suffix in [None, u'']:
608 kwargs['suffix'] = '.tmp'
609 else:
610 if not suffix.startswith('.'):
611 suffix = '.' + suffix
612 kwargs['suffix'] = suffix
613
614 f = tempfile.NamedTemporaryFile(**kwargs)
615 filename = f.name
616 f.close()
617
618 return filename
619
621 """Import a module from any location."""
622
623 remove_path = always_remove_path or False
624 if module_path not in sys.path:
625 _log.info('appending to sys.path: [%s]' % module_path)
626 sys.path.append(module_path)
627 remove_path = True
628
629 _log.debug('will remove import path: %s', remove_path)
630
631 if module_name.endswith('.py'):
632 module_name = module_name[:-3]
633
634 try:
635 module = __import__(module_name)
636 except StandardError:
637 _log.exception('cannot __import__() module [%s] from [%s]' % (module_name, module_path))
638 while module_path in sys.path:
639 sys.path.remove(module_path)
640 raise
641
642 _log.info('imported module [%s] as [%s]' % (module_name, module))
643 if remove_path:
644 while module_path in sys.path:
645 sys.path.remove(module_path)
646
647 return module
648
649
650
651 _kB = 1024
652 _MB = 1024 * _kB
653 _GB = 1024 * _MB
654 _TB = 1024 * _GB
655 _PB = 1024 * _TB
656
658 if size == 1:
659 return template % _('1 Byte')
660 if size < 10 * _kB:
661 return template % _('%s Bytes') % size
662 if size < _MB:
663 return template % u'%.1f kB' % (float(size) / _kB)
664 if size < _GB:
665 return template % u'%.1f MB' % (float(size) / _MB)
666 if size < _TB:
667 return template % u'%.1f GB' % (float(size) / _GB)
668 if size < _PB:
669 return template % u'%.1f TB' % (float(size) / _TB)
670 return template % u'%.1f PB' % (float(size) / _PB)
671
672 -def bool2subst(boolean=None, true_return=True, false_return=False, none_return=None):
673 if boolean is None:
674 return none_return
675 if boolean is True:
676 return true_return
677 if boolean is False:
678 return false_return
679 raise ValueError('bool2subst(): <boolean> arg must be either of True, False, None')
680
681 -def bool2str(boolean=None, true_str='True', false_str='False'):
682 return bool2subst (
683 boolean = bool(boolean),
684 true_return = true_str,
685 false_return = false_str
686 )
687
688 -def none_if(value=None, none_equivalent=None):
689 """Modelled after the SQL NULLIF function."""
690 if value == none_equivalent:
691 return None
692 return value
693
694 -def coalesce(initial=None, instead=None, template_initial=None, template_instead=None, none_equivalents=None, function_initial=None):
695 """Modelled after the SQL coalesce function.
696
697 To be used to simplify constructs like:
698
699 if initial is None (or in none_equivalents):
700 real_value = (template_instead % instead) or instead
701 else:
702 real_value = (template_initial % initial) or initial
703 print real_value
704
705 @param initial: the value to be tested for <None>
706 @type initial: any Python type, must have a __str__ method if template_initial is not None
707 @param instead: the value to be returned if <initial> is None
708 @type instead: any Python type, must have a __str__ method if template_instead is not None
709 @param template_initial: if <initial> is returned replace the value into this template, must contain one <%s>
710 @type template_initial: string or None
711 @param template_instead: if <instead> is returned replace the value into this template, must contain one <%s>
712 @type template_instead: string or None
713
714 Ideas:
715 - list of insteads: initial, [instead, template], [instead, template], [instead, template], template_initial, ...
716 """
717 if none_equivalents is None:
718 none_equivalents = [None]
719
720 if initial in none_equivalents:
721
722 if template_instead is None:
723 return instead
724
725 return template_instead % instead
726
727 if function_initial is not None:
728 funcname, args = function_initial
729 func = getattr(initial, funcname)
730 initial = func(args)
731
732 if template_initial is None:
733 return initial
734
735 try:
736 return template_initial % initial
737 except TypeError:
738 return template_initial
739
741 val = match_obj.group(0).lower()
742 if val in ['von', 'van', 'de', 'la', 'l', 'der', 'den']:
743 return val
744 buf = list(val)
745 buf[0] = buf[0].upper()
746 for part in ['mac', 'mc', 'de', 'la']:
747 if len(val) > len(part) and val[:len(part)] == part:
748 buf[len(part)] = buf[len(part)].upper()
749 return ''.join(buf)
750
752 """Capitalize the first character but leave the rest alone.
753
754 Note that we must be careful about the locale, this may
755 have issues ! However, for UTF strings it should just work.
756 """
757 if (mode is None) or (mode == CAPS_NONE):
758 return text
759
760 if mode == CAPS_FIRST:
761 if len(text) == 1:
762 return text[0].upper()
763 return text[0].upper() + text[1:]
764
765 if mode == CAPS_ALLCAPS:
766 return text.upper()
767
768 if mode == CAPS_FIRST_ONLY:
769 if len(text) == 1:
770 return text[0].upper()
771 return text[0].upper() + text[1:].lower()
772
773 if mode == CAPS_WORDS:
774 return regex.sub(ur'(\w)(\w+)', lambda x: x.group(1).upper() + x.group(2).lower(), text)
775
776 if mode == CAPS_NAMES:
777
778 return capitalize(text=text, mode=CAPS_FIRST)
779
780 print "ERROR: invalid capitalization mode: [%s], leaving input as is" % mode
781 return text
782
804
805 -def wrap(text=None, width=None, initial_indent=u'', subsequent_indent=u'', eol=u'\n'):
806 """A word-wrap function that preserves existing line breaks
807 and most spaces in the text. Expects that existing line
808 breaks are posix newlines (\n).
809 """
810 wrapped = initial_indent + reduce (
811 lambda line, word, width=width: '%s%s%s' % (
812 line,
813 ' \n'[(len(line) - line.rfind('\n') - 1 + len(word.split('\n',1)[0]) >= width)],
814 word
815 ),
816 text.split(' ')
817 )
818
819 if subsequent_indent != u'':
820 wrapped = (u'\n%s' % subsequent_indent).join(wrapped.split('\n'))
821
822 if eol != u'\n':
823 wrapped = wrapped.replace('\n', eol)
824
825 return wrapped
826
827 -def unwrap(text=None, max_length=None, strip_whitespace=True, remove_empty_lines=True, line_separator = u' // '):
828
829 text = text.replace(u'\r', u'')
830 lines = text.split(u'\n')
831 text = u''
832 for line in lines:
833
834 if strip_whitespace:
835 line = line.strip().strip(u'\t').strip()
836
837 if remove_empty_lines:
838 if line == u'':
839 continue
840
841 text += (u'%s%s' % (line, line_separator))
842
843 text = text.rstrip(line_separator)
844
845 if max_length is not None:
846 text = text[:max_length]
847
848 text = text.rstrip(line_separator)
849
850 return text
851
853 """check for special latex-characters and transform them"""
854
855 text = text.replace(u'\\', u'$\\backslash$')
856 text = text.replace(u'{', u'\\{')
857 text = text.replace(u'}', u'\\}')
858 text = text.replace(u'%', u'\\%')
859 text = text.replace(u'&', u'\\&')
860 text = text.replace(u'#', u'\\#')
861 text = text.replace(u'$', u'\\$')
862 text = text.replace(u'_', u'\\_')
863
864 text = text.replace(u'^', u'\\verb#^#')
865 text = text.replace('~','\\verb#~#')
866
867 return text
868
895
896
897
898
899
900 __icon_serpent = \
901 """x\xdae\x8f\xb1\x0e\x83 \x10\x86w\x9f\xe2\x92\x1blb\xf2\x07\x96\xeaH:0\xd6\
902 \xc1\x85\xd5\x98N5\xa5\xef?\xf5N\xd0\x8a\xdcA\xc2\xf7qw\x84\xdb\xfa\xb5\xcd\
903 \xd4\xda;\xc9\x1a\xc8\xb6\xcd<\xb5\xa0\x85\x1e\xeb\xbc\xbc7b!\xf6\xdeHl\x1c\
904 \x94\x073\xec<*\xf7\xbe\xf7\x99\x9d\xb21~\xe7.\xf5\x1f\x1c\xd3\xbdVlL\xc2\
905 \xcf\xf8ye\xd0\x00\x90\x0etH \x84\x80B\xaa\x8a\x88\x85\xc4(U\x9d$\xfeR;\xc5J\
906 \xa6\x01\xbbt9\xceR\xc8\x81e_$\x98\xb9\x9c\xa9\x8d,y\xa9t\xc8\xcf\x152\xe0x\
907 \xe9$\xf5\x07\x95\x0cD\x95t:\xb1\x92\xae\x9cI\xa8~\x84\x1f\xe0\xa3ec"""
908
910
911 paths = gmPaths(app_name = u'gnumed', wx = wx)
912
913 candidates = [
914 os.path.join(paths.system_app_data_dir, 'bitmaps', 'gm_icon-serpent_and_gnu.png'),
915 os.path.join(paths.local_base_dir, 'bitmaps', 'gm_icon-serpent_and_gnu.png'),
916 os.path.join(paths.system_app_data_dir, 'bitmaps', 'serpent.png'),
917 os.path.join(paths.local_base_dir, 'bitmaps', 'serpent.png')
918 ]
919
920 found_as = None
921 for candidate in candidates:
922 try:
923 open(candidate, 'r').close()
924 found_as = candidate
925 break
926 except IOError:
927 _log.debug('icon not found in [%s]', candidate)
928
929 if found_as is None:
930 _log.warning('no icon file found, falling back to builtin (ugly) icon')
931 icon_bmp_data = wx.BitmapFromXPMData(cPickle.loads(zlib.decompress(__icon_serpent)))
932 icon.CopyFromBitmap(icon_bmp_data)
933 else:
934 _log.debug('icon found in [%s]', found_as)
935 icon = wx.EmptyIcon()
936 try:
937 icon.LoadFile(found_as, wx.BITMAP_TYPE_ANY)
938 except AttributeError:
939 _log.exception(u"this platform doesn't support wx.Icon().LoadFile()")
940
941 return icon
942
943
944
945 if __name__ == '__main__':
946
947 if len(sys.argv) < 2:
948 sys.exit()
949
950 if sys.argv[1] != 'test':
951 sys.exit()
952
953
1009
1011
1012 import datetime as dt
1013 print coalesce(initial = dt.datetime.now(), template_initial = u'-- %s --', function_initial = ('strftime', u'%Y-%m-%d'))
1014
1015 print 'testing coalesce()'
1016 print "------------------"
1017 tests = [
1018 [None, 'something other than <None>', None, None, 'something other than <None>'],
1019 ['Captain', 'Mr.', '%s.'[:4], 'Mr.', 'Capt.'],
1020 ['value to test', 'test 3 failed', 'template with "%s" included', None, 'template with "value to test" included'],
1021 ['value to test', 'test 4 failed', 'template with value not included', None, 'template with value not included'],
1022 [None, 'initial value was None', 'template_initial: %s', None, 'initial value was None'],
1023 [None, 'initial value was None', 'template_initial: %%(abc)s', None, 'initial value was None']
1024 ]
1025 passed = True
1026 for test in tests:
1027 result = coalesce (
1028 initial = test[0],
1029 instead = test[1],
1030 template_initial = test[2],
1031 template_instead = test[3]
1032 )
1033 if result != test[4]:
1034 print "ERROR"
1035 print "coalesce: (%s, %s, %s, %s)" % (test[0], test[1], test[2], test[3])
1036 print "expected:", test[4]
1037 print "received:", result
1038 passed = False
1039
1040 if passed:
1041 print "passed"
1042 else:
1043 print "failed"
1044 return passed
1045
1047 print 'testing capitalize() ...'
1048 success = True
1049 pairs = [
1050
1051 [u'Boot', u'Boot', CAPS_FIRST_ONLY],
1052 [u'boot', u'Boot', CAPS_FIRST_ONLY],
1053 [u'booT', u'Boot', CAPS_FIRST_ONLY],
1054 [u'BoOt', u'Boot', CAPS_FIRST_ONLY],
1055 [u'boots-Schau', u'Boots-Schau', CAPS_WORDS],
1056 [u'boots-sChau', u'Boots-Schau', CAPS_WORDS],
1057 [u'boot camp', u'Boot Camp', CAPS_WORDS],
1058 [u'fahrner-Kampe', u'Fahrner-Kampe', CAPS_NAMES],
1059 [u'häkkönen', u'Häkkönen', CAPS_NAMES],
1060 [u'McBurney', u'McBurney', CAPS_NAMES],
1061 [u'mcBurney', u'McBurney', CAPS_NAMES],
1062 [u'blumberg', u'Blumberg', CAPS_NAMES],
1063 [u'roVsing', u'RoVsing', CAPS_NAMES],
1064 [u'Özdemir', u'Özdemir', CAPS_NAMES],
1065 [u'özdemir', u'Özdemir', CAPS_NAMES],
1066 ]
1067 for pair in pairs:
1068 result = capitalize(pair[0], pair[2])
1069 if result != pair[1]:
1070 success = False
1071 print 'ERROR (caps mode %s): "%s" -> "%s", expected "%s"' % (pair[2], pair[0], result, pair[1])
1072
1073 if success:
1074 print "... SUCCESS"
1075
1076 return success
1077
1079 print "testing import_module_from_directory()"
1080 path = sys.argv[1]
1081 name = sys.argv[2]
1082 try:
1083 mod = import_module_from_directory(module_path = path, module_name = name)
1084 except:
1085 print "module import failed, see log"
1086 return False
1087
1088 print "module import succeeded", mod
1089 print dir(mod)
1090 return True
1091
1093 print "testing mkdir()"
1094 mkdir(sys.argv[1])
1095
1097 msg = u"""
1098 To: %s
1099 From: %s
1100 Subject: gmTools test suite mail
1101
1102 This is a test mail from the gmTools.py module.
1103 """ % (default_mail_receiver, default_mail_sender)
1104 print "mail sending succeeded:", send_mail (
1105 receiver = [default_mail_receiver, u'karsten.hilbert@gmx.net'],
1106 message = msg,
1107 auth = {'user': default_mail_sender, 'password': u'gnumed-at-gmx-net'},
1108 debug = True,
1109 attachments = [sys.argv[0]]
1110 )
1111
1113 print "testing gmPaths()"
1114 print "-----------------"
1115 paths = gmPaths(wx=None, app_name='gnumed')
1116 print "user config dir:", paths.user_config_dir
1117 print "system config dir:", paths.system_config_dir
1118 print "local base dir:", paths.local_base_dir
1119 print "system app data dir:", paths.system_app_data_dir
1120 print "working directory :", paths.working_dir
1121
1123 print "testing none_if()"
1124 print "-----------------"
1125 tests = [
1126 [None, None, None],
1127 ['a', 'a', None],
1128 ['a', 'b', 'a'],
1129 ['a', None, 'a'],
1130 [None, 'a', None],
1131 [1, 1, None],
1132 [1, 2, 1],
1133 [1, None, 1],
1134 [None, 1, None]
1135 ]
1136
1137 for test in tests:
1138 if none_if(value = test[0], none_equivalent = test[1]) != test[2]:
1139 print 'ERROR: none_if(%s) returned [%s], expected [%s]' % (test[0], none_if(test[0], test[1]), test[2])
1140
1141 return True
1142
1144 tests = [
1145 [True, 'Yes', 'Yes', 'Yes'],
1146 [False, 'OK', 'not OK', 'not OK']
1147 ]
1148 for test in tests:
1149 if bool2str(test[0], test[1], test[2]) != test[3]:
1150 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])
1151
1152 return True
1153
1155
1156 print bool2subst(True, 'True', 'False', 'is None')
1157 print bool2subst(False, 'True', 'False', 'is None')
1158 print bool2subst(None, 'True', 'False', 'is None')
1159
1166
1168 print "testing size2str()"
1169 print "------------------"
1170 tests = [0, 1, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000, 1000000000000, 10000000000000]
1171 for test in tests:
1172 print size2str(test)
1173
1175
1176 test = """
1177 second line\n
1178 3rd starts with tab \n
1179 4th with a space \n
1180
1181 6th
1182
1183 """
1184 print unwrap(text = test, max_length = 25)
1185
1187 test = 'line 1\nline 2\nline 3'
1188
1189 print "wrap 5-6-7 initial 0, subsequent 0"
1190 print wrap(test, 5)
1191 print
1192 print wrap(test, 6)
1193 print
1194 print wrap(test, 7)
1195 print "-------"
1196 raw_input()
1197 print "wrap 5 initial 1-1-3, subsequent 1-3-1"
1198 print wrap(test, 5, u' ', u' ')
1199 print
1200 print wrap(test, 5, u' ', u' ')
1201 print
1202 print wrap(test, 5, u' ', u' ')
1203 print "-------"
1204 raw_input()
1205 print "wrap 6 initial 1-1-3, subsequent 1-3-1"
1206 print wrap(test, 6, u' ', u' ')
1207 print
1208 print wrap(test, 6, u' ', u' ')
1209 print
1210 print wrap(test, 6, u' ', u' ')
1211 print "-------"
1212 raw_input()
1213 print "wrap 7 initial 1-1-3, subsequent 1-3-1"
1214 print wrap(test, 7, u' ', u' ')
1215 print
1216 print wrap(test, 7, u' ', u' ')
1217 print
1218 print wrap(test, 7, u' ', u' ')
1219
1221
1222 test_data = [
1223 ('http://www.gnumed.de/downloads/gnumed-versions.txt', None, None, False),
1224 ('file:///home/ncq/gm-versions.txt', None, None, False),
1225 ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.1', False),
1226 ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.1', True),
1227 ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.5', True)
1228 ]
1229
1230 for test in test_data:
1231 print "arguments:", test
1232 found, msg = check_for_update(test[0], test[1], test[2], test[3])
1233 print msg
1234
1235 return
1236
1238 print '%s: %s' % (sys.argv[2], file2md5(sys.argv[2]))
1239
1240
1241
1242
1243
1244
1245
1246 test_gmPaths()
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258