| Home | Trees | Indices | Help |
|
|---|
|
|
1 # -*- coding: utf8 -*-
2 __doc__ = """GNUmed general tools."""
3
4 #===========================================================================
5 __author__ = "K. Hilbert <Karsten.Hilbert@gmx.net>"
6 __license__ = "GPL v2 or later (details at http://www.gnu.org)"
7
8 # std libs
9 import re as regex, sys, os, os.path, csv, tempfile, logging, hashlib
10 import platform
11 import subprocess
12 import decimal
13 import cPickle, zlib
14 import xml.sax.saxutils as xml_tools
15
16
17 # GNUmed libs
18 if __name__ == '__main__':
19 # for testing:
20 logging.basicConfig(level = logging.DEBUG)
21 sys.path.insert(0, '../../')
22 from Gnumed.pycommon import gmI18N
23 gmI18N.activate_locale()
24 gmI18N.install_domain()
25
26 from Gnumed.pycommon import gmBorg
27
28
29 _log = logging.getLogger('gm.tools')
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
41 u_currency_pound = u'\u00A3' # Pound sign
42 u_currency_sign = u'\u00A4' # generic currency sign
43 u_currency_yen = u'\u00A5' # Yen sign
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_multiply = u'\u00D7' # x
52 u_triangular_bullet = u'\u2023' # triangular bullet (>)
53 u_ellipsis = u'\u2026' # ...
54 u_euro = u'\u20AC' # EURO sign
55 u_numero = u'\u2116' # No. / # sign
56 u_down_left_arrow = u'\u21B5' # <-'
57 u_left_arrow = u'\u2190' # <--
58 u_right_arrow = u'\u2192' # -->
59 u_left_arrow_with_tail = u'\u21a2' # <--<
60 u_sum = u'\u2211'
61 u_almost_equal_to = u'\u2248' # approximately / nearly / roughly
62 u_corresponds_to = u'\u2258'
63 u_infinity = u'\u221E'
64 u_diameter = u'\u2300'
65 u_checkmark_crossed_out = u'\u237B'
66 u_box_horiz_single = u'\u2500'
67 u_box_horiz_4dashes = u'\u2508'
68 u_box_top_double = u'\u2550'
69 u_box_top_left_double_single = u'\u2552'
70 u_box_top_right_double_single = u'\u2555'
71 u_box_top_left_arc = u'\u256d'
72 u_box_bottom_right_arc = u'\u256f'
73 u_box_bottom_left_arc = u'\u2570'
74 u_box_horiz_light_heavy = u'\u257c'
75 u_box_horiz_heavy_light = u'\u257e'
76 u_skull_and_crossbones = u'\u2620'
77 u_frowning_face = u'\u2639'
78 u_smiling_face = u'\u263a'
79 u_black_heart = u'\u2665'
80 u_checkmark_thin = u'\u2713'
81 u_checkmark_thick = u'\u2714'
82 u_writing_hand = u'\u270d'
83 u_pencil_1 = u'\u270e'
84 u_pencil_2 = u'\u270f'
85 u_pencil_3 = u'\u2710'
86 u_latin_cross = u'\u271d'
87 u_kanji_yen = u'\u5186' # Yen kanji
88 u_replacement_character = u'\ufffd'
89 u_link_symbol = u'\u1f517'
90
91 #===========================================================================
93
94 print ".========================================================"
95 print "| Unhandled exception caught !"
96 print "| Type :", t
97 print "| Value:", v
98 print "`========================================================"
99 _log.critical('unhandled exception caught', exc_info = (t,v,tb))
100 sys.__excepthook__(t,v,tb)
101 #===========================================================================
102 # path level operations
103 #---------------------------------------------------------------------------
105 try:
106 os.makedirs(directory)
107 except OSError, e:
108 if (e.errno == 17) and not os.path.isdir(directory):
109 raise
110 return True
111
112 #---------------------------------------------------------------------------
114 """This class provides the following paths:
115
116 .home_dir
117 .local_base_dir
118 .working_dir
119 .user_config_dir
120 .system_config_dir
121 .system_app_data_dir
122 .tmp_dir (readonly)
123 """
125 """Setup pathes.
126
127 <app_name> will default to (name of the script - .py)
128 """
129 try:
130 self.already_inited
131 return
132 except AttributeError:
133 pass
134
135 self.init_paths(app_name=app_name, wx=wx)
136 self.already_inited = True
137 #--------------------------------------
138 # public API
139 #--------------------------------------
141
142 if wx is None:
143 _log.debug('wxPython not available')
144 _log.debug('detecting paths directly')
145
146 if app_name is None:
147 app_name, ext = os.path.splitext(os.path.basename(sys.argv[0]))
148 _log.info('app name detected as [%s]', app_name)
149 else:
150 _log.info('app name passed in as [%s]', app_name)
151
152 # the user home, doesn't work in Wine so work around that
153 self.__home_dir = None
154
155 # where the main script (the "binary") is installed
156 if getattr(sys, 'frozen', False):
157 _log.info('frozen app, installed into temporary path')
158 # this would find the path of *THIS* file
159 #self.local_base_dir = os.path.dirname(__file__)
160 # while this is documented on the web, the ${_MEIPASS2} does not exist
161 #self.local_base_dir = os.environ.get('_MEIPASS2')
162 # this is what Martin Zibricky <mzibr.public@gmail.com> told us to use
163 # when asking about this on pyinstaller@googlegroups.com
164 #self.local_base_dir = sys._MEIPASS
165 # however, we are --onedir, so we should look at sys.executable
166 # as per the pyinstaller manual
167 self.local_base_dir = os.path.dirname(sys.executable)
168 else:
169 self.local_base_dir = os.path.abspath(os.path.dirname(sys.argv[0]))
170
171 # the current working dir at the OS
172 self.working_dir = os.path.abspath(os.curdir)
173
174 # user-specific config dir, usually below the home dir
175 mkdir(os.path.join(self.home_dir, '.%s' % app_name))
176 self.user_config_dir = os.path.join(self.home_dir, '.%s' % app_name)
177
178 # system-wide config dir, usually below /etc/ under UN*X
179 try:
180 self.system_config_dir = os.path.join('/etc', app_name)
181 except ValueError:
182 #self.system_config_dir = self.local_base_dir
183 self.system_config_dir = self.user_config_dir
184
185 # system-wide application data dir
186 try:
187 self.system_app_data_dir = os.path.join(sys.prefix, 'share', app_name)
188 except ValueError:
189 self.system_app_data_dir = self.local_base_dir
190
191 # temporary directory
192 try:
193 self.__tmp_dir_already_set
194 _log.debug('temp dir already set')
195 except AttributeError:
196 tmp_base = os.path.join(tempfile.gettempdir(), app_name)
197 mkdir(tmp_base)
198 _log.info('previous temp dir: %s', tempfile.gettempdir())
199 tempfile.tempdir = tmp_base
200 _log.info('intermediate temp dir: %s', tempfile.gettempdir())
201 self.tmp_dir = tempfile.mkdtemp(prefix = r'gm-')
202
203 self.__log_paths()
204 if wx is None:
205 return True
206
207 # retry with wxPython
208 _log.debug('re-detecting paths with wxPython')
209
210 std_paths = wx.StandardPaths.Get()
211 _log.info('wxPython app name is [%s]', wx.GetApp().GetAppName())
212
213 # user-specific config dir, usually below the home dir
214 mkdir(os.path.join(std_paths.GetUserConfigDir(), '.%s' % app_name))
215 self.user_config_dir = os.path.join(std_paths.GetUserConfigDir(), '.%s' % app_name)
216
217 # system-wide config dir, usually below /etc/ under UN*X
218 try:
219 tmp = std_paths.GetConfigDir()
220 if not tmp.endswith(app_name):
221 tmp = os.path.join(tmp, app_name)
222 self.system_config_dir = tmp
223 except ValueError:
224 # leave it at what it was from direct detection
225 pass
226
227 # system-wide application data dir
228 # Robin attests that the following doesn't always
229 # give sane values on Windows, so IFDEF it
230 if 'wxMSW' in wx.PlatformInfo:
231 _log.warning('this platform (wxMSW) sometimes returns a broken value for the system-wide application data dir')
232 else:
233 try:
234 self.system_app_data_dir = std_paths.GetDataDir()
235 except ValueError:
236 pass
237
238 self.__log_paths()
239 return True
240 #--------------------------------------
242 _log.debug('sys.argv[0]: %s', sys.argv[0])
243 _log.debug('sys.executable: %s', sys.executable)
244 _log.debug('sys._MEIPASS: %s', getattr(sys, '_MEIPASS', '<not found>'))
245 _log.debug('os.environ["_MEIPASS2"]: %s', os.environ.get('_MEIPASS2', '<not found>'))
246 _log.debug('__file__ : %s', __file__)
247 _log.debug('local application base dir: %s', self.local_base_dir)
248 _log.debug('current working dir: %s', self.working_dir)
249 _log.debug('user home dir: %s', self.home_dir)
250 _log.debug('user-specific config dir: %s', self.user_config_dir)
251 _log.debug('system-wide config dir: %s', self.system_config_dir)
252 _log.debug('system-wide application data dir: %s', self.system_app_data_dir)
253 _log.debug('temporary dir: %s', self.tmp_dir)
254 #--------------------------------------
255 # properties
256 #--------------------------------------
258 if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)):
259 msg = '[%s:user_config_dir]: invalid path [%s]' % (self.__class__.__name__, path)
260 _log.error(msg)
261 raise ValueError(msg)
262 self.__user_config_dir = path
263
266
267 user_config_dir = property(_get_user_config_dir, _set_user_config_dir)
268 #--------------------------------------
270 if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)):
271 msg = '[%s:system_config_dir]: invalid path [%s]' % (self.__class__.__name__, path)
272 _log.error(msg)
273 raise ValueError(msg)
274 self.__system_config_dir = path
275
278
279 system_config_dir = property(_get_system_config_dir, _set_system_config_dir)
280 #--------------------------------------
282 if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)):
283 msg = '[%s:system_app_data_dir]: invalid path [%s]' % (self.__class__.__name__, path)
284 _log.error(msg)
285 raise ValueError(msg)
286 self.__system_app_data_dir = path
287
290
291 system_app_data_dir = property(_get_system_app_data_dir, _set_system_app_data_dir)
292 #--------------------------------------
295
297 if self.__home_dir is not None:
298 return self.__home_dir
299
300 tmp = os.path.expanduser('~')
301 if tmp == '~':
302 _log.error('this platform does not expand ~ properly')
303 try:
304 tmp = os.environ['USERPROFILE']
305 except KeyError:
306 _log.error('cannot access $USERPROFILE in environment')
307
308 if not (
309 os.access(tmp, os.R_OK)
310 and
311 os.access(tmp, os.X_OK)
312 and
313 os.access(tmp, os.W_OK)
314 ):
315 msg = '[%s:home_dir]: invalid path [%s]' % (self.__class__.__name__, tmp)
316 _log.error(msg)
317 raise ValueError(msg)
318
319 self.__home_dir = tmp
320 return self.__home_dir
321
322 home_dir = property(_get_home_dir, _set_home_dir)
323 #--------------------------------------
325 if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)):
326 msg = '[%s:tmp_dir]: invalid path [%s]' % (self.__class__.__name__, path)
327 _log.error(msg)
328 raise ValueError(msg)
329 _log.debug('previous temp dir: %s', tempfile.gettempdir())
330 self.__tmp_dir = path
331 tempfile.tempdir = self.__tmp_dir
332 self.__tmp_dir_already_set = True
333
336
337 tmp_dir = property(_get_tmp_dir, _set_tmp_dir)
338 #===========================================================================
339 # file related tools
340 #---------------------------------------------------------------------------
342
343 if platform.system() == 'Windows':
344 exec_name = 'gpg.exe'
345 else:
346 exec_name = 'gpg'
347
348 tmp, fname = os.path.split(filename)
349 basename, tmp = os.path.splitext(fname)
350 filename_decrypted = get_unique_filename(prefix = '%s-decrypted-' % basename)
351
352 args = [exec_name, '--verbose', '--batch', '--yes', '--passphrase-fd', '0', '--output', filename_decrypted, '--decrypt', filename]
353 _log.debug('GnuPG args: %s' % str(args))
354
355 try:
356 gpg = subprocess.Popen (
357 args = args,
358 stdin = subprocess.PIPE,
359 stdout = subprocess.PIPE,
360 stderr = subprocess.PIPE,
361 close_fds = False
362 )
363 except (OSError, ValueError, subprocess.CalledProcessError):
364 _log.exception('there was a problem executing gpg')
365 gmDispatcher.send(signal = u'statustext', msg = _('Error running GnuPG. Cannot decrypt data.'), beep = True)
366 return
367
368 out, error = gpg.communicate(passphrase)
369 _log.debug('gpg returned [%s]', gpg.returncode)
370 if gpg.returncode != 0:
371 _log.debug('GnuPG STDOUT:\n%s', out)
372 _log.debug('GnuPG STDERR:\n%s', error)
373 return None
374
375 return filename_decrypted
376 #---------------------------------------------------------------------------
378 blocksize = 2**10 * 128 # 128k, since md5 uses 128 byte blocks
379 _log.debug('md5(%s): <%s> byte blocks', filename, blocksize)
380
381 f = open(filename, 'rb')
382
383 md5 = hashlib.md5()
384 while True:
385 data = f.read(blocksize)
386 if not data:
387 break
388 md5.update(data)
389
390 _log.debug('md5(%s): %s', filename, md5.hexdigest())
391
392 if return_hex:
393 return md5.hexdigest()
394 return md5.digest()
395 #---------------------------------------------------------------------------
399
400 #def utf_8_encoder(unicode_csv_data):
401 # for line in unicode_csv_data:
402 # yield line.encode('utf-8')
403
404 default_csv_reader_rest_key = u'list_of_values_of_unknown_fields'
405
407
408 # csv.py doesn't do Unicode; encode temporarily as UTF-8:
409 try:
410 is_dict_reader = kwargs['dict']
411 del kwargs['dict']
412 if is_dict_reader is not True:
413 raise KeyError
414 kwargs['restkey'] = default_csv_reader_rest_key
415 csv_reader = csv.DictReader(unicode2charset_encoder(unicode_csv_data), dialect=dialect, **kwargs)
416 except KeyError:
417 is_dict_reader = False
418 csv_reader = csv.reader(unicode2charset_encoder(unicode_csv_data), dialect=dialect, **kwargs)
419
420 for row in csv_reader:
421 # decode ENCODING back to Unicode, cell by cell:
422 if is_dict_reader:
423 for key in row.keys():
424 if key == default_csv_reader_rest_key:
425 old_data = row[key]
426 new_data = []
427 for val in old_data:
428 new_data.append(unicode(val, encoding))
429 row[key] = new_data
430 if default_csv_reader_rest_key not in csv_reader.fieldnames:
431 csv_reader.fieldnames.append(default_csv_reader_rest_key)
432 else:
433 row[key] = unicode(row[key], encoding)
434 yield row
435 else:
436 yield [ unicode(cell, encoding) for cell in row ]
437 #yield [unicode(cell, 'utf-8') for cell in row]
438 #---------------------------------------------------------------------------
440 """This introduces a race condition between the file.close() and
441 actually using the filename.
442
443 The file will not exist after calling this function.
444 """
445 if tmp_dir is not None:
446 if (
447 not os.access(tmp_dir, os.F_OK)
448 or
449 not os.access(tmp_dir, os.X_OK | os.W_OK)
450 ):
451 _log.info('cannot find temporary dir [%s], using system default', tmp_dir)
452 tmp_dir = None
453
454 kwargs = {'dir': tmp_dir}
455
456 if prefix is None:
457 kwargs['prefix'] = 'gnumed-'
458 else:
459 kwargs['prefix'] = prefix
460
461 if suffix in [None, u'']:
462 kwargs['suffix'] = '.tmp'
463 else:
464 if not suffix.startswith('.'):
465 suffix = '.' + suffix
466 kwargs['suffix'] = suffix
467
468 f = tempfile.NamedTemporaryFile(**kwargs)
469 filename = f.name
470 f.close()
471
472 return filename
473 #===========================================================================
474 -def import_module_from_directory(module_path=None, module_name=None, always_remove_path=False):
475 """Import a module from any location."""
476
477 remove_path = always_remove_path or False
478 if module_path not in sys.path:
479 _log.info('appending to sys.path: [%s]' % module_path)
480 sys.path.append(module_path)
481 remove_path = True
482
483 _log.debug('will remove import path: %s', remove_path)
484
485 if module_name.endswith('.py'):
486 module_name = module_name[:-3]
487
488 try:
489 module = __import__(module_name)
490 except StandardError:
491 _log.exception('cannot __import__() module [%s] from [%s]' % (module_name, module_path))
492 while module_path in sys.path:
493 sys.path.remove(module_path)
494 raise
495
496 _log.info('imported module [%s] as [%s]' % (module_name, module))
497 if remove_path:
498 while module_path in sys.path:
499 sys.path.remove(module_path)
500
501 return module
502 #===========================================================================
503 # text related tools
504 #---------------------------------------------------------------------------
505 _kB = 1024
506 _MB = 1024 * _kB
507 _GB = 1024 * _MB
508 _TB = 1024 * _GB
509 _PB = 1024 * _TB
510 #---------------------------------------------------------------------------
512 if size == 1:
513 return template % _('1 Byte')
514 if size < 10 * _kB:
515 return template % _('%s Bytes') % size
516 if size < _MB:
517 return template % u'%.1f kB' % (float(size) / _kB)
518 if size < _GB:
519 return template % u'%.1f MB' % (float(size) / _MB)
520 if size < _TB:
521 return template % u'%.1f GB' % (float(size) / _GB)
522 if size < _PB:
523 return template % u'%.1f TB' % (float(size) / _TB)
524 return template % u'%.1f PB' % (float(size) / _PB)
525 #---------------------------------------------------------------------------
527 if boolean is None:
528 return none_return
529 if boolean:
530 return true_return
531 if not boolean:
532 return false_return
533 raise ValueError('bool2subst(): <boolean> arg must be either of True, False, None')
534 #---------------------------------------------------------------------------
536 return bool2subst (
537 boolean = bool(boolean),
538 true_return = true_str,
539 false_return = false_str
540 )
541 #---------------------------------------------------------------------------
543 """Modelled after the SQL NULLIF function."""
544 if value is None:
545 return None
546 if strip_string:
547 stripped = value.strip()
548 else:
549 stripped = value
550 if stripped == none_equivalent:
551 return None
552 return value
553 #---------------------------------------------------------------------------
554 -def coalesce(initial=None, instead=None, template_initial=None, template_instead=None, none_equivalents=None, function_initial=None):
555 """Modelled after the SQL coalesce function.
556
557 To be used to simplify constructs like:
558
559 if initial is None (or in none_equivalents):
560 real_value = (template_instead % instead) or instead
561 else:
562 real_value = (template_initial % initial) or initial
563 print real_value
564
565 @param initial: the value to be tested for <None>
566 @type initial: any Python type, must have a __str__ method if template_initial is not None
567 @param instead: the value to be returned if <initial> is None
568 @type instead: any Python type, must have a __str__ method if template_instead is not None
569 @param template_initial: if <initial> is returned replace the value into this template, must contain one <%s>
570 @type template_initial: string or None
571 @param template_instead: if <instead> is returned replace the value into this template, must contain one <%s>
572 @type template_instead: string or None
573
574 example:
575 function_initial = ('strftime', '%Y-%m-%d')
576
577 Ideas:
578 - list of insteads: initial, [instead, template], [instead, template], [instead, template], template_initial, ...
579 """
580 if none_equivalents is None:
581 none_equivalents = [None]
582
583 if initial in none_equivalents:
584
585 if template_instead is None:
586 return instead
587
588 return template_instead % instead
589
590 if function_initial is not None:
591 funcname, args = function_initial
592 func = getattr(initial, funcname)
593 initial = func(args)
594
595 if template_initial is None:
596 return initial
597
598 try:
599 return template_initial % initial
600 except TypeError:
601 return template_initial
602 #---------------------------------------------------------------------------
604 val = match_obj.group(0).lower()
605 if val in ['von', 'van', 'de', 'la', 'l', 'der', 'den']: # FIXME: this needs to expand, configurable ?
606 return val
607 buf = list(val)
608 buf[0] = buf[0].upper()
609 for part in ['mac', 'mc', 'de', 'la']:
610 if len(val) > len(part) and val[:len(part)] == part:
611 buf[len(part)] = buf[len(part)].upper()
612 return ''.join(buf)
613 #---------------------------------------------------------------------------
615 """Capitalize the first character but leave the rest alone.
616
617 Note that we must be careful about the locale, this may
618 have issues ! However, for UTF strings it should just work.
619 """
620 if (mode is None) or (mode == CAPS_NONE):
621 return text
622
623 if mode == CAPS_FIRST:
624 if len(text) == 1:
625 return text[0].upper()
626 return text[0].upper() + text[1:]
627
628 if mode == CAPS_ALLCAPS:
629 return text.upper()
630
631 if mode == CAPS_FIRST_ONLY:
632 if len(text) == 1:
633 return text[0].upper()
634 return text[0].upper() + text[1:].lower()
635
636 if mode == CAPS_WORDS:
637 return regex.sub(ur'(\w)(\w+)', lambda x: x.group(1).upper() + x.group(2).lower(), text)
638
639 if mode == CAPS_NAMES:
640 #return regex.sub(r'\w+', __cap_name, text)
641 return capitalize(text=text, mode=CAPS_FIRST) # until fixed
642
643 print "ERROR: invalid capitalization mode: [%s], leaving input as is" % mode
644 return text
645 #---------------------------------------------------------------------------
647
648 if isinstance(initial, decimal.Decimal):
649 return True, initial
650
651 val = initial
652
653 # float ? -> to string first
654 if type(val) == type(float(1.4)):
655 val = str(val)
656
657 # string ? -> "," to "."
658 if isinstance(val, basestring):
659 val = val.replace(',', '.', 1)
660 val = val.strip()
661
662 try:
663 d = decimal.Decimal(val)
664 return True, d
665 except (TypeError, decimal.InvalidOperation):
666 return False, val
667 #---------------------------------------------------------------------------
669
670 val = initial
671
672 # string ? -> "," to "."
673 if isinstance(val, basestring):
674 val = val.replace(',', '.', 1)
675 val = val.strip()
676
677 try:
678 int_val = int(val)
679 except (TypeError, ValueError):
680 _log.exception('int(%s) failed', val)
681 return False, val
682
683 if minval is not None:
684 if int_val < minval:
685 _log.debug('%s < min (%s)', val, minval)
686 return False, val
687 if maxval is not None:
688 if int_val > maxval:
689 _log.debug('%s > max (%s)', val, maxval)
690 return False, val
691
692 return True, int_val
693 #---------------------------------------------------------------------------
695 if lines is None:
696 lines = text.split(eol)
697
698 while True:
699 if lines[0].strip(eol).strip() != u'':
700 break
701 lines = lines[1:]
702
703 if return_list:
704 return lines
705
706 return eol.join(lines)
707 #---------------------------------------------------------------------------
709 if lines is None:
710 lines = text.split(eol)
711
712 while True:
713 if lines[-1].strip(eol).strip() != u'':
714 break
715 lines = lines[:-1]
716
717 if return_list:
718 return lines
719
720 return eol.join(lines)
721 #---------------------------------------------------------------------------
723 """A word-wrap function that preserves existing line breaks
724 and most spaces in the text. Expects that existing line
725 breaks are posix newlines (\n).
726 """
727 if width is None:
728 return text
729 wrapped = initial_indent + reduce (
730 lambda line, word, width=width: '%s%s%s' % (
731 line,
732 ' \n'[(len(line) - line.rfind('\n') - 1 + len(word.split('\n',1)[0]) >= width)],
733 word
734 ),
735 text.split(' ')
736 )
737
738 if subsequent_indent != u'':
739 wrapped = (u'\n%s' % subsequent_indent).join(wrapped.split('\n'))
740
741 if eol != u'\n':
742 wrapped = wrapped.replace('\n', eol)
743
744 return wrapped
745 #---------------------------------------------------------------------------
746 -def unwrap(text=None, max_length=None, strip_whitespace=True, remove_empty_lines=True, line_separator = u' // '):
747
748 text = text.replace(u'\r', u'')
749 lines = text.split(u'\n')
750 text = u''
751 for line in lines:
752
753 if strip_whitespace:
754 line = line.strip().strip(u'\t').strip()
755
756 if remove_empty_lines:
757 if line == u'':
758 continue
759
760 text += (u'%s%s' % (line, line_separator))
761
762 text = text.rstrip(line_separator)
763
764 if max_length is not None:
765 text = text[:max_length]
766
767 text = text.rstrip(line_separator)
768
769 return text
770 #---------------------------------------------------------------------------
774 #---------------------------------------------------------------------------
776 """check for special TeX characters and transform them"""
777
778 text = text.replace(u'\\', u'\\textbackslash')
779 text = text.replace(u'^', u'\\textasciicircum')
780 text = text.replace('~','\\textasciitilde')
781
782 text = text.replace(u'{', u'\\{')
783 text = text.replace(u'}', u'\\}')
784 text = text.replace(u'%', u'\\%')
785 text = text.replace(u'&', u'\\&')
786 text = text.replace(u'#', u'\\#')
787 text = text.replace(u'$', u'\\$')
788 text = text.replace(u'_', u'\\_')
789
790 if replace_known_unicode:
791 # this should NOT be replaced for Xe(La)Tex
792 text = text.replace(u_euro, u'\\EUR')
793
794 return text
795 #---------------------------------------------------------------------------
797 # a web search did not reveal anything else for Xe(La)Tex
798 # as opposed to LaTeX, except true unicode chars
799 return tex_escape_string(text = text, replace_known_unicode = False)
800 #---------------------------------------------------------------------------
802 """Obtains entry from standard input.
803
804 prompt: Prompt text to display in standard output
805 default: Default value (for user to press enter only)
806 CTRL-C: aborts and returns None
807 """
808 if prompt is None:
809 msg = u'(CTRL-C aborts)'
810 else:
811 msg = u'%s (CTRL-C aborts)' % prompt
812
813 if default is None:
814 msg = msg + u': '
815 else:
816 msg = u'%s [%s]: ' % (msg, default)
817
818 try:
819 usr_input = raw_input(msg)
820 except KeyboardInterrupt:
821 return None
822
823 if usr_input == '':
824 return default
825
826 return usr_input
827
828 #===========================================================================
829 # image handling tools
830 #---------------------------------------------------------------------------
831 # builtin (ugly but tried and true) fallback icon
832 __icon_serpent = \
833 """x\xdae\x8f\xb1\x0e\x83 \x10\x86w\x9f\xe2\x92\x1blb\xf2\x07\x96\xeaH:0\xd6\
834 \xc1\x85\xd5\x98N5\xa5\xef?\xf5N\xd0\x8a\xdcA\xc2\xf7qw\x84\xdb\xfa\xb5\xcd\
835 \xd4\xda;\xc9\x1a\xc8\xb6\xcd<\xb5\xa0\x85\x1e\xeb\xbc\xbc7b!\xf6\xdeHl\x1c\
836 \x94\x073\xec<*\xf7\xbe\xf7\x99\x9d\xb21~\xe7.\xf5\x1f\x1c\xd3\xbdVlL\xc2\
837 \xcf\xf8ye\xd0\x00\x90\x0etH \x84\x80B\xaa\x8a\x88\x85\xc4(U\x9d$\xfeR;\xc5J\
838 \xa6\x01\xbbt9\xceR\xc8\x81e_$\x98\xb9\x9c\xa9\x8d,y\xa9t\xc8\xcf\x152\xe0x\
839 \xe9$\xf5\x07\x95\x0cD\x95t:\xb1\x92\xae\x9cI\xa8~\x84\x1f\xe0\xa3ec"""
840
842
843 paths = gmPaths(app_name = u'gnumed', wx = wx)
844
845 candidates = [
846 os.path.join(paths.system_app_data_dir, 'bitmaps', 'gm_icon-serpent_and_gnu.png'),
847 os.path.join(paths.local_base_dir, 'bitmaps', 'gm_icon-serpent_and_gnu.png'),
848 os.path.join(paths.system_app_data_dir, 'bitmaps', 'serpent.png'),
849 os.path.join(paths.local_base_dir, 'bitmaps', 'serpent.png')
850 ]
851
852 found_as = None
853 for candidate in candidates:
854 try:
855 open(candidate, 'r').close()
856 found_as = candidate
857 break
858 except IOError:
859 _log.debug('icon not found in [%s]', candidate)
860
861 if found_as is None:
862 _log.warning('no icon file found, falling back to builtin (ugly) icon')
863 icon_bmp_data = wx.BitmapFromXPMData(cPickle.loads(zlib.decompress(__icon_serpent)))
864 icon.CopyFromBitmap(icon_bmp_data)
865 else:
866 _log.debug('icon found in [%s]', found_as)
867 icon = wx.EmptyIcon()
868 try:
869 icon.LoadFile(found_as, wx.BITMAP_TYPE_ANY) #_PNG
870 except AttributeError:
871 _log.exception(u"this platform doesn't support wx.Icon().LoadFile()")
872
873 return icon
874 #===========================================================================
875 # main
876 #---------------------------------------------------------------------------
877 if __name__ == '__main__':
878
879 if len(sys.argv) < 2:
880 sys.exit()
881
882 if sys.argv[1] != 'test':
883 sys.exit()
884
885 #-----------------------------------------------------------------------
887
888 tests = [
889 [None, False],
890
891 ['', False],
892 [' 0 ', True, 0],
893
894 [0, True, 0],
895 [0.0, True, 0],
896 [.0, True, 0],
897 ['0', True, 0],
898 ['0.0', True, 0],
899 ['0,0', True, 0],
900 ['00.0', True, 0],
901 ['.0', True, 0],
902 [',0', True, 0],
903
904 [0.1, True, decimal.Decimal('0.1')],
905 [.01, True, decimal.Decimal('0.01')],
906 ['0.1', True, decimal.Decimal('0.1')],
907 ['0,1', True, decimal.Decimal('0.1')],
908 ['00.1', True, decimal.Decimal('0.1')],
909 ['.1', True, decimal.Decimal('0.1')],
910 [',1', True, decimal.Decimal('0.1')],
911
912 [1, True, 1],
913 [1.0, True, 1],
914 ['1', True, 1],
915 ['1.', True, 1],
916 ['1,', True, 1],
917 ['1.0', True, 1],
918 ['1,0', True, 1],
919 ['01.0', True, 1],
920 ['01,0', True, 1],
921 [' 01, ', True, 1],
922
923 [decimal.Decimal('1.1'), True, decimal.Decimal('1.1')]
924 ]
925 for test in tests:
926 conversion_worked, result = input2decimal(initial = test[0])
927
928 expected2work = test[1]
929
930 if conversion_worked:
931 if expected2work:
932 if result == test[2]:
933 continue
934 else:
935 print "ERROR (conversion result wrong): >%s<, expected >%s<, got >%s<" % (test[0], test[2], result)
936 else:
937 print "ERROR (conversion worked but was expected to fail): >%s<, got >%s<" % (test[0], result)
938 else:
939 if not expected2work:
940 continue
941 else:
942 print "ERROR (conversion failed but was expected to work): >%s<, expected >%s<" % (test[0], test[2])
943 #-----------------------------------------------------------------------
948 #-----------------------------------------------------------------------
950
951 import datetime as dt
952 print coalesce(initial = dt.datetime.now(), template_initial = u'-- %s --', function_initial = ('strftime', u'%Y-%m-%d'))
953
954 print 'testing coalesce()'
955 print "------------------"
956 tests = [
957 [None, 'something other than <None>', None, None, 'something other than <None>'],
958 ['Captain', 'Mr.', '%s.'[:4], 'Mr.', 'Capt.'],
959 ['value to test', 'test 3 failed', 'template with "%s" included', None, 'template with "value to test" included'],
960 ['value to test', 'test 4 failed', 'template with value not included', None, 'template with value not included'],
961 [None, 'initial value was None', 'template_initial: %s', None, 'initial value was None'],
962 [None, 'initial value was None', 'template_initial: %%(abc)s', None, 'initial value was None']
963 ]
964 passed = True
965 for test in tests:
966 result = coalesce (
967 initial = test[0],
968 instead = test[1],
969 template_initial = test[2],
970 template_instead = test[3]
971 )
972 if result != test[4]:
973 print "ERROR"
974 print "coalesce: (%s, %s, %s, %s)" % (test[0], test[1], test[2], test[3])
975 print "expected:", test[4]
976 print "received:", result
977 passed = False
978
979 if passed:
980 print "passed"
981 else:
982 print "failed"
983 return passed
984 #-----------------------------------------------------------------------
986 print 'testing capitalize() ...'
987 success = True
988 pairs = [
989 # [original, expected result, CAPS mode]
990 [u'Boot', u'Boot', CAPS_FIRST_ONLY],
991 [u'boot', u'Boot', CAPS_FIRST_ONLY],
992 [u'booT', u'Boot', CAPS_FIRST_ONLY],
993 [u'BoOt', u'Boot', CAPS_FIRST_ONLY],
994 [u'boots-Schau', u'Boots-Schau', CAPS_WORDS],
995 [u'boots-sChau', u'Boots-Schau', CAPS_WORDS],
996 [u'boot camp', u'Boot Camp', CAPS_WORDS],
997 [u'fahrner-Kampe', u'Fahrner-Kampe', CAPS_NAMES],
998 [u'häkkönen', u'Häkkönen', CAPS_NAMES],
999 [u'McBurney', u'McBurney', CAPS_NAMES],
1000 [u'mcBurney', u'McBurney', CAPS_NAMES],
1001 [u'blumberg', u'Blumberg', CAPS_NAMES],
1002 [u'roVsing', u'RoVsing', CAPS_NAMES],
1003 [u'Özdemir', u'Özdemir', CAPS_NAMES],
1004 [u'özdemir', u'Özdemir', CAPS_NAMES],
1005 ]
1006 for pair in pairs:
1007 result = capitalize(pair[0], pair[2])
1008 if result != pair[1]:
1009 success = False
1010 print 'ERROR (caps mode %s): "%s" -> "%s", expected "%s"' % (pair[2], pair[0], result, pair[1])
1011
1012 if success:
1013 print "... SUCCESS"
1014
1015 return success
1016 #-----------------------------------------------------------------------
1018 print "testing import_module_from_directory()"
1019 path = sys.argv[1]
1020 name = sys.argv[2]
1021 try:
1022 mod = import_module_from_directory(module_path = path, module_name = name)
1023 except:
1024 print "module import failed, see log"
1025 return False
1026
1027 print "module import succeeded", mod
1028 print dir(mod)
1029 return True
1030 #-----------------------------------------------------------------------
1034 #-----------------------------------------------------------------------
1036 print "testing gmPaths()"
1037 print "-----------------"
1038 paths = gmPaths(wx=None, app_name='gnumed')
1039 print "user config dir:", paths.user_config_dir
1040 print "system config dir:", paths.system_config_dir
1041 print "local base dir:", paths.local_base_dir
1042 print "system app data dir:", paths.system_app_data_dir
1043 print "working directory :", paths.working_dir
1044 print "temp directory :", paths.tmp_dir
1045 #-----------------------------------------------------------------------
1047 print "testing none_if()"
1048 print "-----------------"
1049 tests = [
1050 [None, None, None],
1051 ['a', 'a', None],
1052 ['a', 'b', 'a'],
1053 ['a', None, 'a'],
1054 [None, 'a', None],
1055 [1, 1, None],
1056 [1, 2, 1],
1057 [1, None, 1],
1058 [None, 1, None]
1059 ]
1060
1061 for test in tests:
1062 if none_if(value = test[0], none_equivalent = test[1]) != test[2]:
1063 print 'ERROR: none_if(%s) returned [%s], expected [%s]' % (test[0], none_if(test[0], test[1]), test[2])
1064
1065 return True
1066 #-----------------------------------------------------------------------
1068 tests = [
1069 [True, 'Yes', 'Yes', 'Yes'],
1070 [False, 'OK', 'not OK', 'not OK']
1071 ]
1072 for test in tests:
1073 if bool2str(test[0], test[1], test[2]) != test[3]:
1074 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])
1075
1076 return True
1077 #-----------------------------------------------------------------------
1079
1080 print bool2subst(True, 'True', 'False', 'is None')
1081 print bool2subst(False, 'True', 'False', 'is None')
1082 print bool2subst(None, 'True', 'False', 'is None')
1083 #-----------------------------------------------------------------------
1085 print get_unique_filename()
1086 print get_unique_filename(prefix='test-')
1087 print get_unique_filename(suffix='tst')
1088 print get_unique_filename(prefix='test-', suffix='tst')
1089 print get_unique_filename(tmp_dir='/home/ncq/Archiv/')
1090 #-----------------------------------------------------------------------
1092 print "testing size2str()"
1093 print "------------------"
1094 tests = [0, 1, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000, 1000000000000, 10000000000000]
1095 for test in tests:
1096 print size2str(test)
1097 #-----------------------------------------------------------------------
1099
1100 test = """
1101 second line\n
1102 3rd starts with tab \n
1103 4th with a space \n
1104
1105 6th
1106
1107 """
1108 print unwrap(text = test, max_length = 25)
1109 #-----------------------------------------------------------------------
1111 test = 'line 1\nline 2\nline 3'
1112
1113 print "wrap 5-6-7 initial 0, subsequent 0"
1114 print wrap(test, 5)
1115 print
1116 print wrap(test, 6)
1117 print
1118 print wrap(test, 7)
1119 print "-------"
1120 raw_input()
1121 print "wrap 5 initial 1-1-3, subsequent 1-3-1"
1122 print wrap(test, 5, u' ', u' ')
1123 print
1124 print wrap(test, 5, u' ', u' ')
1125 print
1126 print wrap(test, 5, u' ', u' ')
1127 print "-------"
1128 raw_input()
1129 print "wrap 6 initial 1-1-3, subsequent 1-3-1"
1130 print wrap(test, 6, u' ', u' ')
1131 print
1132 print wrap(test, 6, u' ', u' ')
1133 print
1134 print wrap(test, 6, u' ', u' ')
1135 print "-------"
1136 raw_input()
1137 print "wrap 7 initial 1-1-3, subsequent 1-3-1"
1138 print wrap(test, 7, u' ', u' ')
1139 print
1140 print wrap(test, 7, u' ', u' ')
1141 print
1142 print wrap(test, 7, u' ', u' ')
1143 #-----------------------------------------------------------------------
1145 print '%s: %s' % (sys.argv[2], file2md5(sys.argv[2]))
1146 #-----------------------------------------------------------------------
1148 print u_link_symbol * 10
1149 #-----------------------------------------------------------------------
1151 print xml_escape_string(u'<')
1152 print xml_escape_string(u'>')
1153 print xml_escape_string(u'&')
1154 #-----------------------------------------------------------------------
1156 fname = gpg_decrypt_file(filename = sys.argv[2], passphrase = sys.argv[3])
1157 if fname is not None:
1158 print "successfully decrypted:", fname
1159 #-----------------------------------------------------------------------
1161 tests = [
1162 u'one line, no embedded line breaks ',
1163 u'one line\nwith embedded\nline\nbreaks\n '
1164 ]
1165 for test in tests:
1166 print 'as list:'
1167 print strip_trailing_empty_lines(text = test, eol=u'\n', return_list = True)
1168 print 'as string:'
1169 print u'>>>%s<<<' % strip_trailing_empty_lines(text = test, eol=u'\n', return_list = False)
1170 tests = [
1171 ['list', 'without', 'empty', 'trailing', 'lines'],
1172 ['list', 'with', 'empty', 'trailing', 'lines', '', ' ', '']
1173 ]
1174 for test in tests:
1175 print 'as list:'
1176 print strip_trailing_empty_lines(lines = test, eol = u'\n', return_list = True)
1177 print 'as string:'
1178 print strip_trailing_empty_lines(lines = test, eol = u'\n', return_list = False)
1179 #-----------------------------------------------------------------------
1180 #test_coalesce()
1181 #test_capitalize()
1182 #test_import_module()
1183 #test_mkdir()
1184 #test_gmPaths()
1185 #test_none_if()
1186 #test_bool2str()
1187 #test_bool2subst()
1188 #test_get_unique_filename()
1189 #test_size2str()
1190 #test_wrap()
1191 #test_input2decimal()
1192 #test_input2int()
1193 #test_unwrap()
1194 #test_md5()
1195 #test_unicode()
1196 #test_xml_escape()
1197 #test_gpg_decrypt()
1198 test_strip_trailing_empty_lines()
1199
1200 #===========================================================================
1201
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Fri Feb 1 03:56:29 2013 | http://epydoc.sourceforge.net |