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