| Trees | Indices | Help |
|
|---|
|
|
1 # -*- coding: utf8 -*-
2 __doc__ = """GNUmed general tools."""
3
4 #===========================================================================
5 # $Id: gmTools.py,v 1.98 2010/01/17 19:47:10 ncq Exp $
6 # $Source: /cvsroot/gnumed/gnumed/gnumed/client/pycommon/gmTools.py,v $
7 __version__ = "$Revision: 1.98 $"
8 __author__ = "K. Hilbert <Karsten.Hilbert@gmx.net>"
9 __license__ = "GPL (details at http://www.gnu.org)"
10
11 # std libs
12 import re as regex, sys, os, os.path, csv, tempfile, logging
13 import urllib2 as wget, decimal, StringIO, MimeWriter, mimetypes, mimetools
14
15
16 # GNUmed libs
17 if __name__ == '__main__':
18 # for testing:
19 logging.basicConfig(level = logging.DEBUG)
20 sys.path.insert(0, '../../')
21 from Gnumed.pycommon import gmI18N
22 gmI18N.activate_locale()
23 gmI18N.install_domain()
24
25 from Gnumed.pycommon import gmBorg
26
27
28 _log = logging.getLogger('gm.tools')
29 _log.info(__version__)
30
31 # CAPitalization modes:
32 ( CAPS_NONE, # don't touch it
33 CAPS_FIRST, # CAP first char, leave rest as is
34 CAPS_ALLCAPS, # CAP all chars
35 CAPS_WORDS, # CAP first char of every word
36 CAPS_NAMES, # CAP in a way suitable for names (tries to be smart)
37 CAPS_FIRST_ONLY # CAP first char, lowercase the rest
38 ) = range(6)
39
40 default_mail_sender = u'gnumed@gmx.net'
41 default_mail_receiver = u'gnumed-devel@gnu.org'
42 default_mail_server = u'mail.gmx.net'
43
44
45 u_right_double_angle_quote = u'\u00AB' # <<
46 u_registered_trademark = u'\u00AE'
47 u_plus_minus = u'\u00B1'
48 u_left_double_angle_quote = u'\u00BB' # >>
49 u_one_quarter = u'\u00BC'
50 u_one_half = u'\u00BD'
51 u_three_quarters = u'\u00BE'
52 u_ellipsis = u'\u2026'
53 u_left_arrow = u'\u2190'
54 u_right_arrow = u'\u2192'
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
70 #===========================================================================
71 -def check_for_update(url=None, current_branch=None, current_version=None, consider_latest_branch=False):
72 """Check for new releases at <url>.
73
74 Returns (bool, text).
75 True: new release available
76 False: up to date
77 None: don't know
78 """
79 try:
80 remote_file = wget.urlopen(url)
81 except (wget.URLError, ValueError, OSError):
82 _log.exception("cannot retrieve version file from [%s]", url)
83 return (None, _('Cannot retrieve version information from:\n\n%s') % url)
84
85 _log.debug('retrieving version information from [%s]', url)
86
87 from Gnumed.pycommon import gmCfg2
88 cfg = gmCfg2.gmCfgData()
89 try:
90 cfg.add_stream_source(source = 'gm-versions', stream = remote_file)
91 except (UnicodeDecodeError):
92 remote_file.close()
93 _log.exception("cannot read version file from [%s]", url)
94 return (None, _('Cannot read version information from:\n\n%s') % url)
95
96 remote_file.close()
97
98 latest_branch = cfg.get('latest branch', 'branch', source_order = [('gm-versions', 'return')])
99 latest_release_on_latest_branch = cfg.get('branch %s' % latest_branch, 'latest release', source_order = [('gm-versions', 'return')])
100 latest_release_on_current_branch = cfg.get('branch %s' % current_branch, 'latest release', source_order = [('gm-versions', 'return')])
101
102 cfg.remove_source('gm-versions')
103
104 _log.info('current release: %s', current_version)
105 _log.info('current branch: %s', current_branch)
106 _log.info('latest release on current branch: %s', latest_release_on_current_branch)
107 _log.info('latest branch: %s', latest_branch)
108 _log.info('latest release on latest branch: %s', latest_release_on_latest_branch)
109
110 # anything known ?
111 no_release_information_available = (
112 (
113 (latest_release_on_current_branch is None) and
114 (latest_release_on_latest_branch is None)
115 ) or (
116 not consider_latest_branch and
117 (latest_release_on_current_branch is None)
118 )
119 )
120 if no_release_information_available:
121 _log.warning('no release information available')
122 msg = _('There is no version information available from:\n\n%s') % url
123 return (None, msg)
124
125 # up to date ?
126 if consider_latest_branch:
127 _log.debug('latest branch taken into account')
128 if current_version >= latest_release_on_latest_branch:
129 _log.debug('up to date: current version >= latest version on latest branch')
130 return (False, None)
131 if latest_release_on_latest_branch is None:
132 if current_version >= latest_release_on_current_branch:
133 _log.debug('up to date: current version >= latest version on current branch and no latest branch available')
134 return (False, None)
135 else:
136 _log.debug('latest branch not taken into account')
137 if current_version >= latest_release_on_current_branch:
138 _log.debug('up to date: current version >= latest version on current branch')
139 return (False, None)
140
141 new_release_on_current_branch_available = (
142 (latest_release_on_current_branch is not None) and
143 (latest_release_on_current_branch > current_version)
144 )
145 _log.info('%snew release on current branch available', bool2str(new_release_on_current_branch_available, '', 'no '))
146
147 new_release_on_latest_branch_available = (
148 (latest_branch is not None)
149 and
150 (
151 (latest_branch > current_branch) or (
152 (latest_branch == current_branch) and
153 (latest_release_on_latest_branch > current_version)
154 )
155 )
156 )
157 _log.info('%snew release on latest branch available', bool2str(new_release_on_latest_branch_available, '', 'no '))
158
159 if not (new_release_on_current_branch_available or new_release_on_latest_branch_available):
160 _log.debug('up to date: no new releases available')
161 return (False, None)
162
163 # not up to date
164 msg = _('A new version of GNUmed is available.\n\n')
165 msg += _(' Your current version: "%s"\n') % current_version
166 if consider_latest_branch:
167 if new_release_on_current_branch_available:
168 msg += u'\n'
169 msg += _(' New version: "%s"') % latest_release_on_current_branch
170 msg += u'\n'
171 msg += _(' - bug fixes only\n')
172 msg += _(' - database fixups may be needed\n')
173 if new_release_on_latest_branch_available:
174 if current_branch != latest_branch:
175 msg += u'\n'
176 msg += _(' New version: "%s"') % latest_release_on_latest_branch
177 msg += u'\n'
178 msg += _(' - bug fixes and new features\n')
179 msg += _(' - database upgrade required\n')
180 else:
181 msg += u'\n'
182 msg += _(' New version: "%s"') % latest_release_on_current_branch
183 msg += u'\n'
184 msg += _(' - bug fixes only\n')
185 msg += _(' - database fixups may be needed\n')
186
187 msg += u'\n\n'
188 msg += _(
189 'Note, however, that this version may not yet\n'
190 'be available *pre-packaged* for your system.'
191 )
192
193 msg += u'\n\n'
194 msg += _('Details are found on <http://wiki.gnumed.de>.\n')
195 msg += u'\n'
196 msg += _('Version information loaded from:\n\n %s') % url
197
198 return (True, msg)
199 #===========================================================================
203
204 #def utf_8_encoder(unicode_csv_data):
205 # for line in unicode_csv_data:
206 # yield line.encode('utf-8')
207
209 # csv.py doesn't do Unicode; encode temporarily as UTF-8:
210 try:
211 is_dict_reader = kwargs['dict']
212 del kwargs['dict']
213 if is_dict_reader is not True:
214 raise KeyError
215 csv_reader = csv.DictReader(unicode2charset_encoder(unicode_csv_data), dialect=dialect, **kwargs)
216 except KeyError:
217 is_dict_reader = False
218 csv_reader = csv.reader(unicode2charset_encoder(unicode_csv_data), dialect=dialect, **kwargs)
219
220 for row in csv_reader:
221 # decode ENCODING back to Unicode, cell by cell:
222 if is_dict_reader:
223 for key in row.keys():
224 row[key] = unicode(row[key], encoding)
225 yield row
226 else:
227 yield [ unicode(cell, encoding) for cell in row ]
228 #yield [unicode(cell, 'utf-8') for cell in row]
229 #===========================================================================
231
232 print ",========================================================"
233 print "| Unhandled exception caught !"
234 print "| Type :", t
235 print "| Value:", v
236 print "`========================================================"
237 _log.critical('unhandled exception caught', exc_info = (t,v,tb))
238 sys.__excepthook__(t,v,tb)
239 #===========================================================================
241
243 """Setup pathes.
244
245 <app_name> will default to (name of the script - .py)
246 """
247 try:
248 self.already_inited
249 return
250 except AttributeError:
251 pass
252
253 self.init_paths(app_name=app_name, wx=wx)
254 self.already_inited = True
255 #--------------------------------------
256 # public API
257 #--------------------------------------
259
260 if wx is None:
261 _log.debug('wxPython not available')
262 _log.debug('detecting paths directly')
263
264 if app_name is None:
265 app_name, ext = os.path.splitext(os.path.basename(sys.argv[0]))
266 _log.info('app name detected as [%s]', app_name)
267 else:
268 _log.info('app name passed in as [%s]', app_name)
269
270 # the user home, doesn't work in Wine so work around that
271 self.__home_dir = None
272
273 # where the main script (the "binary") is installed
274 self.local_base_dir = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '..', '.'))
275
276 # the current working dir at the OS
277 self.working_dir = os.path.abspath(os.curdir)
278
279 # user-specific config dir, usually below the home dir
280 #mkdir(os.path.expanduser(os.path.join('~', '.%s' % app_name)))
281 #self.user_config_dir = os.path.expanduser(os.path.join('~', '.%s' % app_name))
282 mkdir(os.path.join(self.home_dir, '.%s' % app_name))
283 self.user_config_dir = os.path.join(self.home_dir, '.%s' % app_name)
284
285 # system-wide config dir, usually below /etc/ under UN*X
286 try:
287 self.system_config_dir = os.path.join('/etc', app_name)
288 except ValueError:
289 #self.system_config_dir = self.local_base_dir
290 self.system_config_dir = self.user_config_dir
291
292 # system-wide application data dir
293 try:
294 self.system_app_data_dir = os.path.join(sys.prefix, 'share', app_name)
295 except ValueError:
296 self.system_app_data_dir = self.local_base_dir
297
298 self.__log_paths()
299 if wx is None:
300 return True
301
302 # retry with wxPython
303 _log.debug('re-detecting paths with wxPython')
304
305 std_paths = wx.StandardPaths.Get()
306 _log.info('wxPython app name is [%s]', wx.GetApp().GetAppName())
307
308 # user-specific config dir, usually below the home dir
309 mkdir(os.path.join(std_paths.GetUserConfigDir(), '.%s' % app_name))
310 self.user_config_dir = os.path.join(std_paths.GetUserConfigDir(), '.%s' % app_name)
311
312 # system-wide config dir, usually below /etc/ under UN*X
313 try:
314 tmp = std_paths.GetConfigDir()
315 if not tmp.endswith(app_name):
316 tmp = os.path.join(tmp, app_name)
317 self.system_config_dir = tmp
318 except ValueError:
319 # leave it at what it was from direct detection
320 pass
321
322 # system-wide application data dir
323 # Robin attests that the following doesn't always
324 # give sane values on Windows, so IFDEF it
325 if 'wxMSW' in wx.PlatformInfo:
326 _log.warning('this platform (wxMSW) sometimes returns a broken value for the system-wide application data dir')
327 else:
328 try:
329 self.system_app_data_dir = std_paths.GetDataDir()
330 except ValueError:
331 pass
332
333 self.__log_paths()
334 return True
335 #--------------------------------------
337 _log.debug('sys.argv[0]: %s', sys.argv[0])
338 _log.debug('local application base dir: %s', self.local_base_dir)
339 _log.debug('current working dir: %s', self.working_dir)
340 #_log.debug('user home dir: %s', os.path.expanduser('~'))
341 _log.debug('user home dir: %s', self.home_dir)
342 _log.debug('user-specific config dir: %s', self.user_config_dir)
343 _log.debug('system-wide config dir: %s', self.system_config_dir)
344 _log.debug('system-wide application data dir: %s', self.system_app_data_dir)
345 #--------------------------------------
346 # properties
347 #--------------------------------------
349 if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)):
350 msg = '[%s:user_config_dir]: invalid path [%s]' % (self.__class__.__name__, path)
351 _log.error(msg)
352 raise ValueError(msg)
353 self.__user_config_dir = path
354
357
358 user_config_dir = property(_get_user_config_dir, _set_user_config_dir)
359 #--------------------------------------
361 if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)):
362 msg = '[%s:system_config_dir]: invalid path [%s]' % (self.__class__.__name__, path)
363 _log.error(msg)
364 raise ValueError(msg)
365 self.__system_config_dir = path
366
369
370 system_config_dir = property(_get_system_config_dir, _set_system_config_dir)
371 #--------------------------------------
373 if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)):
374 msg = '[%s:system_app_data_dir]: invalid path [%s]' % (self.__class__.__name__, path)
375 _log.error(msg)
376 raise ValueError(msg)
377 self.__system_app_data_dir = path
378
381
382 system_app_data_dir = property(_get_system_app_data_dir, _set_system_app_data_dir)
383 #--------------------------------------
386
388 if self.__home_dir is not None:
389 return self.__home_dir
390
391 tmp = os.path.expanduser('~')
392 if tmp == '~':
393 _log.error('this platform does not expand ~ properly')
394 try:
395 tmp = os.environ['USERPROFILE']
396 except KeyError:
397 _log.error('cannot access $USERPROFILE in environment')
398
399 if not (
400 os.access(tmp, os.R_OK)
401 and
402 os.access(tmp, os.X_OK)
403 and
404 os.access(tmp, os.W_OK)
405 ):
406 msg = '[%s:home_dir]: invalid path [%s]' % (self.__class__.__name__, tmp)
407 _log.error(msg)
408 raise ValueError(msg)
409
410 self.__home_dir = tmp
411 return self.__home_dir
412
413 home_dir = property(_get_home_dir, _set_home_dir)
414 #===========================================================================
415 -def send_mail(sender=None, receiver=None, message=None, server=None, auth=None, debug=False, subject=None, encoding='quoted-printable', attachments=None):
416
417 if message is None:
418 return False
419
420 message = message.lstrip().lstrip('\r\n').lstrip()
421
422 if sender is None:
423 sender = default_mail_sender
424
425 if receiver is None:
426 receiver = [default_mail_receiver]
427
428 if server is None:
429 server = default_mail_server
430
431 if subject is None:
432 subject = u'gmTools.py: send_mail() test'
433
434 msg = StringIO.StringIO()
435 writer = MimeWriter.MimeWriter(msg)
436 writer.addheader('To', u', '.join(receiver))
437 writer.addheader('From', sender)
438 writer.addheader('Subject', subject[:50].replace('\r', '/').replace('\n', '/'))
439 writer.addheader('MIME-Version', '1.0')
440
441 writer.startmultipartbody('mixed')
442
443 # start with a text/plain part
444 part = writer.nextpart()
445 body = part.startbody('text/plain')
446 part.flushheaders()
447 body.write(message.encode(encoding))
448
449 # now add the attachments
450 if attachments is not None:
451 for a in attachments:
452 filename = os.path.basename(a[0])
453 try:
454 mtype = a[1]
455 encoding = a[2]
456 except IndexError:
457 mtype, encoding = mimetypes.guess_type(a[0])
458 if mtype is None:
459 mtype = 'application/octet-stream'
460 encoding = 'base64'
461 elif mtype == 'text/plain':
462 encoding = 'quoted-printable'
463 else:
464 encoding = 'base64'
465
466 part = writer.nextpart()
467 part.addheader('Content-Transfer-Encoding', encoding)
468 body = part.startbody("%s; name=%s" % (mtype, filename))
469 mimetools.encode(open(a[0], 'rb'), body, encoding)
470
471 writer.lastpart()
472
473 import smtplib
474 session = smtplib.SMTP(server)
475 session.set_debuglevel(debug)
476 if auth is not None:
477 session.login(auth['user'], auth['password'])
478 refused = session.sendmail(sender, receiver, msg.getvalue())
479 session.quit()
480 msg.close()
481 if len(refused) != 0:
482 _log.error("refused recipients: %s" % refused)
483 return False
484
485 return True
486 #-------------------------------------------------------------------------------
487 -def send_mail_old(sender=None, receiver=None, message=None, server=None, auth=None, debug=False, subject=None, encoding='latin1'):
488 """Send an E-Mail.
489
490 <debug>: see smtplib.set_debuglevel()
491 <auth>: {'user': ..., 'password': ...}
492 <receiver>: a list of email addresses
493 """
494 if message is None:
495 return False
496 message = message.lstrip().lstrip('\r\n').lstrip()
497
498 if sender is None:
499 sender = default_mail_sender
500
501 if receiver is None:
502 receiver = [default_mail_receiver]
503
504 if server is None:
505 server = default_mail_server
506
507 if subject is None:
508 subject = u'gmTools.py: send_mail() test'
509
510 body = u"""From: %s
511 To: %s
512 Subject: %s
513
514 %s
515 """ % (sender, u', '.join(receiver), subject[:50].replace('\r', '/').replace('\n', '/'), message)
516
517 import smtplib
518 session = smtplib.SMTP(server)
519 session.set_debuglevel(debug)
520 if auth is not None:
521 session.login(auth['user'], auth['password'])
522 refused = session.sendmail(sender, receiver, body.encode(encoding))
523 session.quit()
524 if len(refused) != 0:
525 _log.error("refused recipients: %s" % refused)
526 return False
527
528 return True
529 #===========================================================================
531 try:
532 os.makedirs(directory)
533 except OSError, e:
534 if (e.errno == 17) and not os.path.isdir(directory):
535 raise
536 return True
537 #---------------------------------------------------------------------------
539 """This introduces a race condition between the file.close() and
540 actually using the filename.
541
542 The file will not exist after calling this function.
543 """
544 if tmp_dir is not None:
545 if (
546 not os.access(tmp_dir, os.F_OK)
547 or
548 not os.access(tmp_dir, os.X_OK | os.W_OK)
549 ):
550 _log.info('cannot find temporary dir [%s], using system default', tmp_dir)
551 tmp_dir = None
552
553 kwargs = {'dir': tmp_dir}
554
555 if prefix is None:
556 kwargs['prefix'] = 'gnumed-'
557 else:
558 kwargs['prefix'] = prefix
559
560 if suffix is None:
561 kwargs['suffix'] = '.tmp'
562 else:
563 if not suffix.startswith('.'):
564 suffix = '.' + suffix
565 kwargs['suffix'] = suffix
566
567 f = tempfile.NamedTemporaryFile(**kwargs)
568 filename = f.name
569 f.close()
570
571 return filename
572 #===========================================================================
574 """Import a module from any location."""
575
576 if module_path not in sys.path:
577 _log.info('appending to sys.path: [%s]' % module_path)
578 sys.path.append(module_path)
579 remove_path = True
580 else:
581 remove_path = False
582
583 if module_name.endswith('.py'):
584 module_name = module_name[:-3]
585
586 try:
587 module = __import__(module_name)
588 except StandardError:
589 _log.exception('cannot __import__() module [%s] from [%s]' % (module_name, module_path))
590 sys.path.remove(module_path)
591 raise
592
593 _log.info('imported module [%s] as [%s]' % (module_name, module))
594 if remove_path:
595 sys.path.remove(module_path)
596
597 return module
598 #===========================================================================
599 # text related tools
600 #---------------------------------------------------------------------------
601 _kB = 1024
602 _MB = 1024 * _kB
603 _GB = 1024 * _MB
604 _TB = 1024 * _GB
605 _PB = 1024 * _TB
606 #---------------------------------------------------------------------------
608 if size == 1:
609 return template % _('1 Byte')
610 if size < 10 * _kB:
611 return template % _('%s Bytes') % size
612 if size < _MB:
613 return template % u'%.1f kB' % (float(size) / _kB)
614 if size < _GB:
615 return template % u'%.1f MB' % (float(size) / _MB)
616 if size < _TB:
617 return template % u'%.1f GB' % (float(size) / _GB)
618 if size < _PB:
619 return template % u'%.1f TB' % (float(size) / _TB)
620 return template % u'%.1f PB' % (float(size) / _PB)
621 #---------------------------------------------------------------------------
623 if boolean is None:
624 return none_return
625 if boolean is True:
626 return true_return
627 if boolean is False:
628 return false_return
629 raise ValueError('bool2subst(): <boolean> arg must be either of True, False, None')
630 #---------------------------------------------------------------------------
632 return bool2subst (
633 boolean = bool(boolean),
634 true_return = true_str,
635 false_return = false_str
636 )
637 #---------------------------------------------------------------------------
639 """Modelled after the SQL NULLIF function."""
640 if value == none_equivalent:
641 return None
642 return value
643 #---------------------------------------------------------------------------
644 -def coalesce(initial=None, instead=None, template_initial=None, template_instead=None, none_equivalents=None):
645 """Modelled after the SQL coalesce function.
646
647 To be used to simplify constructs like:
648
649 if initial is None (or in none_equivalents):
650 real_value = (template_instead % instead) or instead
651 else:
652 real_value = (template_initial % initial) or initial
653 print real_value
654
655 @param initial: the value to be tested for <None>
656 @type initial: any Python type, must have a __str__ method if template_initial is not None
657 @param instead: the value to be returned if <initial> is None
658 @type instead: any Python type, must have a __str__ method if template_instead is not None
659 @param template_initial: if <initial> is returned replace the value into this template, must contain one <%s>
660 @type template_initial: string or None
661 @param template_instead: if <instead> is returned replace the value into this template, must contain one <%s>
662 @type template_instead: string or None
663
664 Ideas:
665 - list of insteads: initial, [instead, template], [instead, template], [instead, template], template_initial, ...
666 """
667 if none_equivalents is None:
668 none_equivalents = [None]
669
670 if initial in none_equivalents:
671
672 if template_instead is None:
673 return instead
674
675 return template_instead % instead
676
677 if template_initial is None:
678 return initial
679
680 try:
681 return template_initial % initial
682 except TypeError:
683 return template_initial
684 #---------------------------------------------------------------------------
686 val = match_obj.group(0).lower()
687 if val in ['von', 'van', 'de', 'la', 'l', 'der', 'den']: # FIXME: this needs to expand, configurable ?
688 return val
689 buf = list(val)
690 buf[0] = buf[0].upper()
691 for part in ['mac', 'mc', 'de', 'la']:
692 if len(val) > len(part) and val[:len(part)] == part:
693 buf[len(part)] = buf[len(part)].upper()
694 return ''.join(buf)
695 #---------------------------------------------------------------------------
697 """Capitalize the first character but leave the rest alone.
698
699 Note that we must be careful about the locale, this may
700 have issues ! However, for UTF strings it should just work.
701 """
702 if (mode is None) or (mode == CAPS_NONE):
703 return text
704
705 if mode == CAPS_FIRST:
706 if len(text) == 1:
707 return text[0].upper()
708 return text[0].upper() + text[1:]
709
710 if mode == CAPS_ALLCAPS:
711 return text.upper()
712
713 if mode == CAPS_FIRST_ONLY:
714 if len(text) == 1:
715 return text[0].upper()
716 return text[0].upper() + text[1:].lower()
717
718 if mode == CAPS_WORDS:
719 return regex.sub(ur'(\w)(\w+)', lambda x: x.group(1).upper() + x.group(2).lower(), text)
720
721 if mode == CAPS_NAMES:
722 #return regex.sub(r'\w+', __cap_name, text)
723 return capitalize(text=text, mode=CAPS_FIRST) # until fixed
724
725 print "ERROR: invalid capitalization mode: [%s], leaving input as is" % mode
726 return text
727 #---------------------------------------------------------------------------
729
730 val = initial
731
732 # float ? -> to string first
733 if type(val) == type(1.4):
734 val = str(val)
735
736 # string ? -> "," to "."
737 if isinstance(val, basestring):
738 val = val.replace(',', '.', 1)
739 val = val.strip()
740 # val = val.lstrip('0')
741 # if val.startswith('.'):
742 # val = '0' + val
743
744 try:
745 d = decimal.Decimal(val)
746 return True, d
747 except (TypeError, decimal.InvalidOperation):
748 return False, val
749 #---------------------------------------------------------------------------
751 """A word-wrap function that preserves existing line breaks
752 and most spaces in the text. Expects that existing line
753 breaks are posix newlines (\n).
754 """
755 wrapped = initial_indent + reduce (
756 lambda line, word, width=width: '%s%s%s' % (
757 line,
758 ' \n'[(len(line) - line.rfind('\n') - 1 + len(word.split('\n',1)[0]) >= width)],
759 word
760 ),
761 text.split(' ')
762 )
763
764 if subsequent_indent != u'':
765 wrapped = (u'\n%s' % subsequent_indent).join(wrapped.split('\n'))
766
767 if eol != u'\n':
768 wrapped = wrapped.replace('\n', eol)
769
770 return wrapped
771 #---------------------------------------------------------------------------
772 -def unwrap(text=None, max_length=None, strip_whitespace=True, remove_empty_lines=True, line_separator = u' // '):
773
774 text = text.replace(u'\r', u'')
775 lines = text.split(u'\n')
776 text = u''
777 for line in lines:
778
779 if strip_whitespace:
780 line = line.strip().strip(u'\t').strip()
781
782 if remove_empty_lines:
783 if line == u'':
784 continue
785
786 text += (u'%s%s' % (line, line_separator))
787
788 text = text.rstrip(line_separator)
789
790 if max_length is not None:
791 text = text[:max_length]
792
793 text = text.rstrip(line_separator)
794
795 return text
796 #---------------------------------------------------------------------------
798 """check for special latex-characters and transform them"""
799
800 text = text.replace(u'\\', u'$\\backslash$')
801 text = text.replace(u'{', u'\\{')
802 text = text.replace(u'}', u'\\}')
803 text = text.replace(u'%', u'\\%')
804 text = text.replace(u'&', u'\\&')
805 text = text.replace(u'#', u'\\#')
806 text = text.replace(u'$', u'\\$')
807 text = text.replace(u'_', u'\\_')
808
809 text = text.replace(u'^', u'\\verb#^#')
810 text = text.replace('~','\\verb#~#')
811
812 return text
813 #===========================================================================
814 # main
815 #---------------------------------------------------------------------------
816 if __name__ == '__main__':
817
818 #-----------------------------------------------------------------------
820
821 tests = [
822 [None, False],
823
824 ['', False],
825 [' 0 ', True, 0],
826
827 [0, True, 0],
828 [0.0, True, 0],
829 [.0, True, 0],
830 ['0', True, 0],
831 ['0.0', True, 0],
832 ['0,0', True, 0],
833 ['00.0', True, 0],
834 ['.0', True, 0],
835 [',0', True, 0],
836
837 [0.1, True, decimal.Decimal('0.1')],
838 [.01, True, decimal.Decimal('0.01')],
839 ['0.1', True, decimal.Decimal('0.1')],
840 ['0,1', True, decimal.Decimal('0.1')],
841 ['00.1', True, decimal.Decimal('0.1')],
842 ['.1', True, decimal.Decimal('0.1')],
843 [',1', True, decimal.Decimal('0.1')],
844
845 [1, True, 1],
846 [1.0, True, 1],
847 ['1', True, 1],
848 ['1.', True, 1],
849 ['1,', True, 1],
850 ['1.0', True, 1],
851 ['1,0', True, 1],
852 ['01.0', True, 1],
853 ['01,0', True, 1],
854 [' 01, ', True, 1],
855 ]
856 for test in tests:
857 conversion_worked, result = input2decimal(initial = test[0])
858
859 expected2work = test[1]
860
861 if conversion_worked:
862 if expected2work:
863 if result == test[2]:
864 continue
865 else:
866 print "ERROR (conversion result wrong): >%s<, expected >%s<, got >%s<" % (test[0], test[2], result)
867 else:
868 print "ERROR (conversion worked but was expected to fail): >%s<, got >%s<" % (test[0], result)
869 else:
870 if not expected2work:
871 continue
872 else:
873 print "ERROR (conversion failed but was expected to work): >%s<, expected >%s<" % (test[0], test[2])
874 #-----------------------------------------------------------------------
876 print 'testing coalesce()'
877 print "------------------"
878 tests = [
879 [None, 'something other than <None>', None, None, 'something other than <None>'],
880 ['Captain', 'Mr.', '%s.'[:4], 'Mr.', 'Capt.'],
881 ['value to test', 'test 3 failed', 'template with "%s" included', None, 'template with "value to test" included'],
882 ['value to test', 'test 4 failed', 'template with value not included', None, 'template with value not included'],
883 [None, 'initial value was None', 'template_initial: %s', None, 'initial value was None'],
884 [None, 'initial value was None', 'template_initial: %%(abc)s', None, 'initial value was None']
885 ]
886 passed = True
887 for test in tests:
888 result = coalesce (
889 initial = test[0],
890 instead = test[1],
891 template_initial = test[2],
892 template_instead = test[3]
893 )
894 if result != test[4]:
895 print "ERROR"
896 print "coalesce: (%s, %s, %s, %s)" % (test[0], test[1], test[2], test[3])
897 print "expected:", test[4]
898 print "received:", result
899 passed = False
900
901 if passed:
902 print "passed"
903 else:
904 print "failed"
905 return passed
906 #-----------------------------------------------------------------------
908 print 'testing capitalize() ...'
909 success = True
910 pairs = [
911 # [original, expected result, CAPS mode]
912 [u'Boot', u'Boot', CAPS_FIRST_ONLY],
913 [u'boot', u'Boot', CAPS_FIRST_ONLY],
914 [u'booT', u'Boot', CAPS_FIRST_ONLY],
915 [u'BoOt', u'Boot', CAPS_FIRST_ONLY],
916 [u'boots-Schau', u'Boots-Schau', CAPS_WORDS],
917 [u'boots-sChau', u'Boots-Schau', CAPS_WORDS],
918 [u'boot camp', u'Boot Camp', CAPS_WORDS],
919 [u'fahrner-Kampe', u'Fahrner-Kampe', CAPS_NAMES],
920 [u'häkkönen', u'Häkkönen', CAPS_NAMES],
921 [u'McBurney', u'McBurney', CAPS_NAMES],
922 [u'mcBurney', u'McBurney', CAPS_NAMES],
923 [u'blumberg', u'Blumberg', CAPS_NAMES],
924 [u'roVsing', u'RoVsing', CAPS_NAMES],
925 [u'Özdemir', u'Özdemir', CAPS_NAMES],
926 [u'özdemir', u'Özdemir', CAPS_NAMES],
927 ]
928 for pair in pairs:
929 result = capitalize(pair[0], pair[2])
930 if result != pair[1]:
931 success = False
932 print 'ERROR (caps mode %s): "%s" -> "%s", expected "%s"' % (pair[2], pair[0], result, pair[1])
933
934 if success:
935 print "... SUCCESS"
936
937 return success
938 #-----------------------------------------------------------------------
940 print "testing import_module_from_directory()"
941 path = sys.argv[1]
942 name = sys.argv[2]
943 try:
944 mod = import_module_from_directory(module_path = path, module_name = name)
945 except:
946 print "module import failed, see log"
947 return False
948
949 print "module import succeeded", mod
950 print dir(mod)
951 return True
952 #-----------------------------------------------------------------------
956 #-----------------------------------------------------------------------
958 msg = u"""
959 To: %s
960 From: %s
961 Subject: gmTools test suite mail
962
963 This is a test mail from the gmTools.py module.
964 """ % (default_mail_receiver, default_mail_sender)
965 print "mail sending succeeded:", send_mail (
966 receiver = [default_mail_receiver, u'karsten.hilbert@gmx.net'],
967 message = msg,
968 auth = {'user': default_mail_sender, 'password': u'gnumed-at-gmx-net'}, # u'gm/bugs/gmx'
969 debug = True,
970 attachments = [sys.argv[0]]
971 )
972 #-----------------------------------------------------------------------
974 print "testing gmPaths()"
975 print "-----------------"
976 paths = gmPaths(wx=None, app_name='gnumed')
977 print "user config dir:", paths.user_config_dir
978 print "system config dir:", paths.system_config_dir
979 print "local base dir:", paths.local_base_dir
980 print "system app data dir:", paths.system_app_data_dir
981 print "working directory :", paths.working_dir
982 #-----------------------------------------------------------------------
984 print "testing none_if()"
985 print "-----------------"
986 tests = [
987 [None, None, None],
988 ['a', 'a', None],
989 ['a', 'b', 'a'],
990 ['a', None, 'a'],
991 [None, 'a', None],
992 [1, 1, None],
993 [1, 2, 1],
994 [1, None, 1],
995 [None, 1, None]
996 ]
997
998 for test in tests:
999 if none_if(value = test[0], none_equivalent = test[1]) != test[2]:
1000 print 'ERROR: none_if(%s) returned [%s], expected [%s]' % (test[0], none_if(test[0], test[1]), test[2])
1001
1002 return True
1003 #-----------------------------------------------------------------------
1005 tests = [
1006 [True, 'Yes', 'Yes', 'Yes'],
1007 [False, 'OK', 'not OK', 'not OK']
1008 ]
1009 for test in tests:
1010 if bool2str(test[0], test[1], test[2]) != test[3]:
1011 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])
1012
1013 return True
1014 #-----------------------------------------------------------------------
1016
1017 print bool2subst(True, 'True', 'False', 'is None')
1018 print bool2subst(False, 'True', 'False', 'is None')
1019 print bool2subst(None, 'True', 'False', 'is None')
1020 #-----------------------------------------------------------------------
1022 print get_unique_filename()
1023 print get_unique_filename(prefix='test-')
1024 print get_unique_filename(suffix='tst')
1025 print get_unique_filename(prefix='test-', suffix='tst')
1026 print get_unique_filename(tmp_dir='/home/ncq/Archiv/')
1027 #-----------------------------------------------------------------------
1029 print "testing size2str()"
1030 print "------------------"
1031 tests = [0, 1, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000, 1000000000000, 10000000000000]
1032 for test in tests:
1033 print size2str(test)
1034 #-----------------------------------------------------------------------
1036
1037 test = """
1038 second line\n
1039 3rd starts with tab \n
1040 4th with a space \n
1041
1042 6th
1043
1044 """
1045 print unwrap(text = test, max_length = 25)
1046 #-----------------------------------------------------------------------
1048 test = 'line 1\nline 2\nline 3'
1049
1050 print "wrap 5-6-7 initial 0, subsequent 0"
1051 print wrap(test, 5)
1052 print
1053 print wrap(test, 6)
1054 print
1055 print wrap(test, 7)
1056 print "-------"
1057 raw_input()
1058 print "wrap 5 initial 1-1-3, subsequent 1-3-1"
1059 print wrap(test, 5, u' ', u' ')
1060 print
1061 print wrap(test, 5, u' ', u' ')
1062 print
1063 print wrap(test, 5, u' ', u' ')
1064 print "-------"
1065 raw_input()
1066 print "wrap 6 initial 1-1-3, subsequent 1-3-1"
1067 print wrap(test, 6, u' ', u' ')
1068 print
1069 print wrap(test, 6, u' ', u' ')
1070 print
1071 print wrap(test, 6, u' ', u' ')
1072 print "-------"
1073 raw_input()
1074 print "wrap 7 initial 1-1-3, subsequent 1-3-1"
1075 print wrap(test, 7, u' ', u' ')
1076 print
1077 print wrap(test, 7, u' ', u' ')
1078 print
1079 print wrap(test, 7, u' ', u' ')
1080 #-----------------------------------------------------------------------
1082
1083 test_data = [
1084 ('http://www.gnumed.de/downloads/gnumed-versions.txt', None, None, False),
1085 ('file:///home/ncq/gm-versions.txt', None, None, False),
1086 ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.1', False),
1087 ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.1', True),
1088 ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.5', True)
1089 ]
1090
1091 for test in test_data:
1092 print "arguments:", test
1093 found, msg = check_for_update(test[0], test[1], test[2], test[3])
1094 print msg
1095
1096 return
1097 #-----------------------------------------------------------------------
1098 if len(sys.argv) > 1 and sys.argv[1] == 'test':
1099
1100 #test_check_for_update()
1101 #test_coalesce()
1102 #test_capitalize()
1103 #test_import_module()
1104 #test_mkdir()
1105 #test_send_mail()
1106 #test_gmPaths()
1107 #test_none_if()
1108 #test_bool2str()
1109 #test_bool2subst()
1110 #test_get_unique_filename()
1111 #test_size2str()
1112 #test_wrap()
1113 #test_input2decimal()
1114 test_unwrap()
1115
1116 #===========================================================================
1117 # $Log: gmTools.py,v $
1118 # Revision 1.98 2010/01/17 19:47:10 ncq
1119 # - add comment on quotes
1120 #
1121 # Revision 1.97 2010/01/15 12:42:46 ncq
1122 # - tex-escape-string
1123 #
1124 # Revision 1.96 2009/12/21 15:02:51 ncq
1125 # - cleanup
1126 #
1127 # Revision 1.95 2009/11/29 15:57:51 ncq
1128 # - must properly initialize is_dict_reader
1129 #
1130 # Revision 1.94 2009/11/15 01:04:30 ncq
1131 # - add smiling/frowning face
1132 #
1133 # Revision 1.93 2009/11/06 15:12:57 ncq
1134 # - add right arrow
1135 #
1136 # Revision 1.92 2009/10/28 16:40:32 ncq
1137 # - add infinity symbol
1138 #
1139 # Revision 1.91 2009/10/21 20:39:18 ncq
1140 # - make unicode csv *dict* reader actually work
1141 # - make intermediate encoding of unicode csv reader configurable
1142 #
1143 # Revision 1.90 2009/09/23 14:32:30 ncq
1144 # - u-corresponds-to
1145 #
1146 # Revision 1.89 2009/09/08 17:15:13 ncq
1147 # - add unwrap() test
1148 #
1149 # Revision 1.88 2009/09/01 22:25:02 ncq
1150 # - enhance coalesce with none_equivalents
1151 #
1152 # Revision 1.87 2009/08/13 12:12:20 ncq
1153 # - slightly better upgrade available message
1154 #
1155 # Revision 1.86 2009/07/15 12:17:14 ncq
1156 # - add latin cross unicode point
1157 # - better error handling on version checking
1158 #
1159 # Revision 1.85 2009/06/10 21:00:43 ncq
1160 # - remove "gm_versions" cfg source after use
1161 #
1162 # Revision 1.84 2009/05/13 10:35:22 ncq
1163 # - some cleanup
1164 #
1165 # Revision 1.83 2009/04/21 16:54:34 ncq
1166 # - fix setting sys app data dir on non-Windows
1167 #
1168 # Revision 1.82 2009/04/19 22:26:25 ncq
1169 # - factor out LOINC handling
1170 # - factor out interval parsing
1171 #
1172 # Revision 1.81 2009/04/14 17:54:48 ncq
1173 # - test under Py2.6
1174 #
1175 # Revision 1.80 2009/04/03 12:29:36 ncq
1176 # - add attachment handling to send_mail
1177 #
1178 # Revision 1.79 2009/04/03 11:08:33 ncq
1179 # - add two more unicode code points
1180 #
1181 # Revision 1.78 2009/04/03 09:37:05 ncq
1182 # - splitter for LOINCDB.TXT
1183 # - improved paths handling (~ doesn't expand under Wine)
1184 #
1185 # Revision 1.77 2009/03/18 14:29:45 ncq
1186 # - add unicode code point for 3/4
1187 # - better fallback for sys app data dir on Windows
1188 #
1189 # Revision 1.76 2009/03/10 14:22:33 ncq
1190 # - add unicode code points
1191 #
1192 # Revision 1.75 2009/03/01 18:10:50 ncq
1193 # - improve update-avail message
1194 #
1195 # Revision 1.74 2009/02/18 13:45:25 ncq
1196 # - get_unique_filename API change
1197 #
1198 # Revision 1.73 2009/01/02 11:38:09 ncq
1199 # - input2decimal + tests
1200 #
1201 # Revision 1.72 2008/12/22 18:58:53 ncq
1202 # - cleanup
1203 #
1204 # Revision 1.71 2008/12/09 23:28:15 ncq
1205 # - better logging
1206 # - always remove aux module path if importing fails
1207 #
1208 # Revision 1.70 2008/11/20 18:47:40 ncq
1209 # - add left arrow unicode
1210 # - fix logging in update check
1211 #
1212 # Revision 1.69 2008/11/03 10:28:55 ncq
1213 # - check_for_update
1214 # - improved logging and wording
1215 # - logic reversal fix
1216 #
1217 # Revision 1.68 2008/10/12 15:48:33 ncq
1218 # - improved wording when checking for updates
1219 #
1220 # Revision 1.67 2008/08/31 16:13:15 ncq
1221 # - cleanup
1222 #
1223 # Revision 1.66 2008/08/28 18:32:24 ncq
1224 # - read latest branch then latest release from branch group
1225 #
1226 # Revision 1.65 2008/08/20 13:53:57 ncq
1227 # - add some coalesce tests
1228 #
1229 # Revision 1.64 2008/07/28 15:43:35 ncq
1230 # - teach wrap() about target EOL
1231 #
1232 # Revision 1.63 2008/07/12 15:30:56 ncq
1233 # - improved coalesce test
1234 #
1235 # Revision 1.62 2008/07/12 15:24:37 ncq
1236 # - impove coalesce to allow template_initial to be returned *instead* of
1237 # initial substituted into the template by not including a substitution
1238 #
1239 # Revision 1.61 2008/07/10 20:51:38 ncq
1240 # - better logging
1241 #
1242 # Revision 1.60 2008/07/10 19:59:09 ncq
1243 # - better logging
1244 # - check whether sys config dir ends in "gnumed"
1245 #
1246 # Revision 1.59 2008/07/07 11:34:41 ncq
1247 # - robustify capsify on single character strings
1248 #
1249 # Revision 1.58 2008/06/28 18:25:01 ncq
1250 # - add unicode Registered TM symbol
1251 #
1252 # Revision 1.57 2008/05/31 16:32:42 ncq
1253 # - a couple of unicode shortcuts
1254 #
1255 # Revision 1.56 2008/05/26 12:05:50 ncq
1256 # - improved wording of update message
1257 # - better handling of CVS tip
1258 #
1259 # Revision 1.55 2008/05/21 15:51:45 ncq
1260 # - if cannot open update URL may throw OSError, so deal with that
1261 #
1262 # Revision 1.54 2008/05/21 14:01:32 ncq
1263 # - add check_for_update and tests
1264 #
1265 # Revision 1.53 2008/05/13 14:09:36 ncq
1266 # - str2interval: support xMxW syntax
1267 #
1268 # Revision 1.52 2008/05/07 15:18:01 ncq
1269 # - i18n str2interval
1270 #
1271 # Revision 1.51 2008/04/16 20:34:43 ncq
1272 # - add bool2subst tests
1273 #
1274 # Revision 1.50 2008/04/11 12:24:39 ncq
1275 # - add initial_indent/subsequent_indent and tests to wrap()
1276 #
1277 # Revision 1.49 2008/03/20 15:29:51 ncq
1278 # - bool2subst() supporting None, make bool2str() use it
1279 #
1280 # Revision 1.48 2008/03/02 15:10:32 ncq
1281 # - truncate exception comment to 50 chars when used as subject
1282 #
1283 # Revision 1.47 2008/01/16 19:42:24 ncq
1284 # - whitespace sync
1285 #
1286 # Revision 1.46 2007/12/23 11:59:40 ncq
1287 # - improved docs
1288 #
1289 # Revision 1.45 2007/12/12 16:24:09 ncq
1290 # - general cleanup
1291 #
1292 # Revision 1.44 2007/12/11 14:33:48 ncq
1293 # - use standard logging module
1294 #
1295 # Revision 1.43 2007/11/28 13:59:23 ncq
1296 # - test improved
1297 #
1298 # Revision 1.42 2007/11/21 13:28:35 ncq
1299 # - enhance send_mail() with subject and encoding
1300 # - handle body formatting
1301 #
1302 # Revision 1.41 2007/10/23 21:23:30 ncq
1303 # - cleanup
1304 #
1305 # Revision 1.40 2007/10/09 10:29:02 ncq
1306 # - clean up import_module_from_directory()
1307 #
1308 # Revision 1.39 2007/10/08 12:48:17 ncq
1309 # - normalize / and \ in import_module_from_directory() so it works on Windows
1310 #
1311 # Revision 1.38 2007/08/29 14:33:56 ncq
1312 # - better document get_unique_filename()
1313 #
1314 # Revision 1.37 2007/08/28 21:47:19 ncq
1315 # - log user home dir
1316 #
1317 # Revision 1.36 2007/08/15 09:18:56 ncq
1318 # - size2str() and test
1319 #
1320 # Revision 1.35 2007/08/07 21:41:02 ncq
1321 # - cPaths -> gmPaths
1322 #
1323 # Revision 1.34 2007/07/13 09:47:38 ncq
1324 # - fix and test suite for get_unique_filename()
1325 #
1326 # Revision 1.33 2007/07/11 21:06:51 ncq
1327 # - improved docs
1328 # - get_unique_filename()
1329 #
1330 # Revision 1.32 2007/07/10 20:45:42 ncq
1331 # - add unicode CSV reader
1332 # - factor out OOo related code
1333 #
1334 # Revision 1.31 2007/06/19 12:43:17 ncq
1335 # - add bool2str() and test
1336 #
1337 # Revision 1.30 2007/06/10 09:56:03 ncq
1338 # - u''ificiation and flags in regex calls
1339 #
1340 # Revision 1.29 2007/05/17 15:12:59 ncq
1341 # - even more careful about pathes
1342 #
1343 # Revision 1.28 2007/05/17 15:10:16 ncq
1344 # - create user config dir if it doesn't exist
1345 #
1346 # Revision 1.27 2007/05/15 08:20:13 ncq
1347 # - ifdef GetDataDir() on wxMSW as per Robin's suggestion
1348 #
1349 # Revision 1.26 2007/05/14 08:35:06 ncq
1350 # - better logging
1351 # - try to handle platforms with broken GetDataDir()
1352 #
1353 # Revision 1.25 2007/05/13 21:20:54 ncq
1354 # - improved logging
1355 #
1356 # Revision 1.24 2007/05/13 20:22:17 ncq
1357 # - log errors
1358 #
1359 # Revision 1.23 2007/05/08 16:03:55 ncq
1360 # - add console exception display handler
1361 #
1362 # Revision 1.22 2007/05/07 12:31:06 ncq
1363 # - improved path handling and testing
1364 # - switch file to utf8
1365 #
1366 # Revision 1.21 2007/04/21 19:38:27 ncq
1367 # - add none_if() and test suite
1368 #
1369 # Revision 1.20 2007/04/19 13:09:52 ncq
1370 # - add cPaths borg and test suite
1371 #
1372 # Revision 1.19 2007/04/09 16:30:31 ncq
1373 # - add send_mail()
1374 #
1375 # Revision 1.18 2007/03/08 16:19:30 ncq
1376 # - typo and cleanup
1377 #
1378 # Revision 1.17 2007/02/17 13:58:11 ncq
1379 # - improved coalesce()
1380 #
1381 # Revision 1.16 2007/02/04 16:43:01 ncq
1382 # - improve capitalize() test suite
1383 # - set coding
1384 #
1385 # Revision 1.15 2007/02/04 16:29:51 ncq
1386 # - make umlauts u''
1387 #
1388 # Revision 1.14 2007/02/04 15:33:28 ncq
1389 # - enhance capitalize() and add mode CONSTS for it
1390 # - however, CAPS_NAMES for now maps to CAPS_FIRST until fixed for Heller-Brunner
1391 # - slightly improved test suite for it
1392 #
1393 # Revision 1.13 2007/01/30 17:38:28 ncq
1394 # - add mkdir() and a test for it
1395 #
1396 # Revision 1.12 2007/01/20 22:04:01 ncq
1397 # - strip ".py" from script name if it is there
1398 #
1399 # Revision 1.11 2007/01/18 12:46:30 ncq
1400 # - add reasonably safe import_module_from_directory() and test
1401 #
1402 # Revision 1.10 2007/01/15 20:20:39 ncq
1403 # - add wrap()
1404 #
1405 # Revision 1.9 2007/01/06 17:05:57 ncq
1406 # - start OOo server if cannot connect to one
1407 # - test suite
1408 #
1409 # Revision 1.8 2006/12/21 10:53:53 ncq
1410 # - document coalesce() better
1411 #
1412 # Revision 1.7 2006/12/18 15:51:12 ncq
1413 # - comment how to start server OOo writer
1414 #
1415 # Revision 1.6 2006/12/17 20:47:16 ncq
1416 # - add open_uri_in_ooo()
1417 #
1418 # Revision 1.5 2006/11/27 23:02:08 ncq
1419 # - add comment
1420 #
1421 # Revision 1.4 2006/11/24 09:52:09 ncq
1422 # - add str2interval() - this will need to end up in an interval input phrasewheel !
1423 # - improve test suite
1424 #
1425 # Revision 1.3 2006/11/20 15:58:10 ncq
1426 # - template handling in coalesce()
1427 #
1428 # Revision 1.2 2006/10/31 16:03:06 ncq
1429 # - add capitalize() and test
1430 #
1431 # Revision 1.1 2006/09/03 08:53:19 ncq
1432 # - first version
1433 #
1434 #
1435
| Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Tue Feb 9 04:02:36 2010 | http://epydoc.sourceforge.net |