| 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 """A word-wrap function that preserves existing line breaks
738 and most spaces in the text. Expects that existing line
739 breaks are posix newlines (\n).
740 """
741 if width is None:
742 return text
743 wrapped = initial_indent + reduce (
744 lambda line, word, width=width: '%s%s%s' % (
745 line,
746 ' \n'[(len(line) - line.rfind('\n') - 1 + len(word.split('\n',1)[0]) >= width)],
747 word
748 ),
749 text.split(' ')
750 )
751
752 if subsequent_indent != u'':
753 wrapped = (u'\n%s' % subsequent_indent).join(wrapped.split('\n'))
754
755 if eol != u'\n':
756 wrapped = wrapped.replace('\n', eol)
757
758 return wrapped
759 #---------------------------------------------------------------------------
760 -def unwrap(text=None, max_length=None, strip_whitespace=True, remove_empty_lines=True, line_separator = u' // '):
761
762 text = text.replace(u'\r', u'')
763 lines = text.split(u'\n')
764 text = u''
765 for line in lines:
766
767 if strip_whitespace:
768 line = line.strip().strip(u'\t').strip()
769
770 if remove_empty_lines:
771 if line == u'':
772 continue
773
774 text += (u'%s%s' % (line, line_separator))
775
776 text = text.rstrip(line_separator)
777
778 if max_length is not None:
779 text = text[:max_length]
780
781 text = text.rstrip(line_separator)
782
783 return text
784 #---------------------------------------------------------------------------
788 #---------------------------------------------------------------------------
790 """check for special TeX characters and transform them"""
791
792 text = text.replace(u'\\', u'\\textbackslash')
793 text = text.replace(u'^', u'\\textasciicircum')
794 text = text.replace(u'~', u'\\textasciitilde')
795
796 text = text.replace(u'{', u'\\{')
797 text = text.replace(u'}', u'\\}')
798 text = text.replace(u'%', u'\\%')
799 text = text.replace(u'&', u'\\&')
800 text = text.replace(u'#', u'\\#')
801 text = text.replace(u'$', u'\\$')
802 text = text.replace(u'_', u'\\_')
803
804 if replace_known_unicode:
805 # this should NOT be replaced for Xe(La)Tex
806 text = text.replace(u_euro, u'\\EUR')
807
808 return text
809 #---------------------------------------------------------------------------
811 # a web search did not reveal anything else for Xe(La)Tex
812 # as opposed to LaTeX, except true unicode chars
813 return tex_escape_string(text = text, replace_known_unicode = False)
814 #---------------------------------------------------------------------------
816 """Obtains entry from standard input.
817
818 prompt: Prompt text to display in standard output
819 default: Default value (for user to press enter only)
820 CTRL-C: aborts and returns None
821 """
822 if prompt is None:
823 msg = u'(CTRL-C aborts)'
824 else:
825 msg = u'%s (CTRL-C aborts)' % prompt
826
827 if default is None:
828 msg = msg + u': '
829 else:
830 msg = u'%s [%s]: ' % (msg, default)
831
832 try:
833 usr_input = raw_input(msg)
834 except KeyboardInterrupt:
835 return None
836
837 if usr_input == '':
838 return default
839
840 return usr_input
841
842 #===========================================================================
843 # image handling tools
844 #---------------------------------------------------------------------------
845 # builtin (ugly but tried and true) fallback icon
846 __icon_serpent = \
847 """x\xdae\x8f\xb1\x0e\x83 \x10\x86w\x9f\xe2\x92\x1blb\xf2\x07\x96\xeaH:0\xd6\
848 \xc1\x85\xd5\x98N5\xa5\xef?\xf5N\xd0\x8a\xdcA\xc2\xf7qw\x84\xdb\xfa\xb5\xcd\
849 \xd4\xda;\xc9\x1a\xc8\xb6\xcd<\xb5\xa0\x85\x1e\xeb\xbc\xbc7b!\xf6\xdeHl\x1c\
850 \x94\x073\xec<*\xf7\xbe\xf7\x99\x9d\xb21~\xe7.\xf5\x1f\x1c\xd3\xbdVlL\xc2\
851 \xcf\xf8ye\xd0\x00\x90\x0etH \x84\x80B\xaa\x8a\x88\x85\xc4(U\x9d$\xfeR;\xc5J\
852 \xa6\x01\xbbt9\xceR\xc8\x81e_$\x98\xb9\x9c\xa9\x8d,y\xa9t\xc8\xcf\x152\xe0x\
853 \xe9$\xf5\x07\x95\x0cD\x95t:\xb1\x92\xae\x9cI\xa8~\x84\x1f\xe0\xa3ec"""
854
856
857 paths = gmPaths(app_name = u'gnumed', wx = wx)
858
859 candidates = [
860 os.path.join(paths.system_app_data_dir, 'bitmaps', 'gm_icon-serpent_and_gnu.png'),
861 os.path.join(paths.local_base_dir, 'bitmaps', 'gm_icon-serpent_and_gnu.png'),
862 os.path.join(paths.system_app_data_dir, 'bitmaps', 'serpent.png'),
863 os.path.join(paths.local_base_dir, 'bitmaps', 'serpent.png')
864 ]
865
866 found_as = None
867 for candidate in candidates:
868 try:
869 open(candidate, 'r').close()
870 found_as = candidate
871 break
872 except IOError:
873 _log.debug('icon not found in [%s]', candidate)
874
875 if found_as is None:
876 _log.warning('no icon file found, falling back to builtin (ugly) icon')
877 icon_bmp_data = wx.BitmapFromXPMData(cPickle.loads(zlib.decompress(__icon_serpent)))
878 icon.CopyFromBitmap(icon_bmp_data)
879 else:
880 _log.debug('icon found in [%s]', found_as)
881 icon = wx.EmptyIcon()
882 try:
883 icon.LoadFile(found_as, wx.BITMAP_TYPE_ANY) #_PNG
884 except AttributeError:
885 _log.exception(u"this platform doesn't support wx.Icon().LoadFile()")
886
887 return icon
888 #===========================================================================
889 # main
890 #---------------------------------------------------------------------------
891 if __name__ == '__main__':
892
893 if len(sys.argv) < 2:
894 sys.exit()
895
896 if sys.argv[1] != 'test':
897 sys.exit()
898
899 #-----------------------------------------------------------------------
901
902 tests = [
903 [None, False],
904
905 ['', False],
906 [' 0 ', True, 0],
907
908 [0, True, 0],
909 [0.0, True, 0],
910 [.0, True, 0],
911 ['0', True, 0],
912 ['0.0', True, 0],
913 ['0,0', True, 0],
914 ['00.0', True, 0],
915 ['.0', True, 0],
916 [',0', True, 0],
917
918 [0.1, True, decimal.Decimal('0.1')],
919 [.01, True, decimal.Decimal('0.01')],
920 ['0.1', True, decimal.Decimal('0.1')],
921 ['0,1', True, decimal.Decimal('0.1')],
922 ['00.1', True, decimal.Decimal('0.1')],
923 ['.1', True, decimal.Decimal('0.1')],
924 [',1', True, decimal.Decimal('0.1')],
925
926 [1, True, 1],
927 [1.0, True, 1],
928 ['1', True, 1],
929 ['1.', True, 1],
930 ['1,', True, 1],
931 ['1.0', True, 1],
932 ['1,0', True, 1],
933 ['01.0', True, 1],
934 ['01,0', True, 1],
935 [' 01, ', True, 1],
936
937 [decimal.Decimal('1.1'), True, decimal.Decimal('1.1')]
938 ]
939 for test in tests:
940 conversion_worked, result = input2decimal(initial = test[0])
941
942 expected2work = test[1]
943
944 if conversion_worked:
945 if expected2work:
946 if result == test[2]:
947 continue
948 else:
949 print "ERROR (conversion result wrong): >%s<, expected >%s<, got >%s<" % (test[0], test[2], result)
950 else:
951 print "ERROR (conversion worked but was expected to fail): >%s<, got >%s<" % (test[0], result)
952 else:
953 if not expected2work:
954 continue
955 else:
956 print "ERROR (conversion failed but was expected to work): >%s<, expected >%s<" % (test[0], test[2])
957 #-----------------------------------------------------------------------
962 #-----------------------------------------------------------------------
964
965 import datetime as dt
966 print coalesce(initial = dt.datetime.now(), template_initial = u'-- %s --', function_initial = ('strftime', u'%Y-%m-%d'))
967
968 print 'testing coalesce()'
969 print "------------------"
970 tests = [
971 [None, 'something other than <None>', None, None, 'something other than <None>'],
972 ['Captain', 'Mr.', '%s.'[:4], 'Mr.', 'Capt.'],
973 ['value to test', 'test 3 failed', 'template with "%s" included', None, 'template with "value to test" included'],
974 ['value to test', 'test 4 failed', 'template with value not included', None, 'template with value not included'],
975 [None, 'initial value was None', 'template_initial: %s', None, 'initial value was None'],
976 [None, 'initial value was None', 'template_initial: %%(abc)s', None, 'initial value was None']
977 ]
978 passed = True
979 for test in tests:
980 result = coalesce (
981 initial = test[0],
982 instead = test[1],
983 template_initial = test[2],
984 template_instead = test[3]
985 )
986 if result != test[4]:
987 print "ERROR"
988 print "coalesce: (%s, %s, %s, %s)" % (test[0], test[1], test[2], test[3])
989 print "expected:", test[4]
990 print "received:", result
991 passed = False
992
993 if passed:
994 print "passed"
995 else:
996 print "failed"
997 return passed
998 #-----------------------------------------------------------------------
1000 print 'testing capitalize() ...'
1001 success = True
1002 pairs = [
1003 # [original, expected result, CAPS mode]
1004 [u'Boot', u'Boot', CAPS_FIRST_ONLY],
1005 [u'boot', u'Boot', CAPS_FIRST_ONLY],
1006 [u'booT', u'Boot', CAPS_FIRST_ONLY],
1007 [u'BoOt', u'Boot', CAPS_FIRST_ONLY],
1008 [u'boots-Schau', u'Boots-Schau', CAPS_WORDS],
1009 [u'boots-sChau', u'Boots-Schau', CAPS_WORDS],
1010 [u'boot camp', u'Boot Camp', CAPS_WORDS],
1011 [u'fahrner-Kampe', u'Fahrner-Kampe', CAPS_NAMES],
1012 [u'häkkönen', u'Häkkönen', CAPS_NAMES],
1013 [u'McBurney', u'McBurney', CAPS_NAMES],
1014 [u'mcBurney', u'McBurney', CAPS_NAMES],
1015 [u'blumberg', u'Blumberg', CAPS_NAMES],
1016 [u'roVsing', u'RoVsing', CAPS_NAMES],
1017 [u'Özdemir', u'Özdemir', CAPS_NAMES],
1018 [u'özdemir', u'Özdemir', CAPS_NAMES],
1019 ]
1020 for pair in pairs:
1021 result = capitalize(pair[0], pair[2])
1022 if result != pair[1]:
1023 success = False
1024 print 'ERROR (caps mode %s): "%s" -> "%s", expected "%s"' % (pair[2], pair[0], result, pair[1])
1025
1026 if success:
1027 print "... SUCCESS"
1028
1029 return success
1030 #-----------------------------------------------------------------------
1032 print "testing import_module_from_directory()"
1033 path = sys.argv[1]
1034 name = sys.argv[2]
1035 try:
1036 mod = import_module_from_directory(module_path = path, module_name = name)
1037 except:
1038 print "module import failed, see log"
1039 return False
1040
1041 print "module import succeeded", mod
1042 print dir(mod)
1043 return True
1044 #-----------------------------------------------------------------------
1048 #-----------------------------------------------------------------------
1050 print "testing gmPaths()"
1051 print "-----------------"
1052 paths = gmPaths(wx=None, app_name='gnumed')
1053 print "user config dir:", paths.user_config_dir
1054 print "system config dir:", paths.system_config_dir
1055 print "local base dir:", paths.local_base_dir
1056 print "system app data dir:", paths.system_app_data_dir
1057 print "working directory :", paths.working_dir
1058 print "temp directory :", paths.tmp_dir
1059 #-----------------------------------------------------------------------
1061 print "testing none_if()"
1062 print "-----------------"
1063 tests = [
1064 [None, None, None],
1065 ['a', 'a', None],
1066 ['a', 'b', 'a'],
1067 ['a', None, 'a'],
1068 [None, 'a', None],
1069 [1, 1, None],
1070 [1, 2, 1],
1071 [1, None, 1],
1072 [None, 1, None]
1073 ]
1074
1075 for test in tests:
1076 if none_if(value = test[0], none_equivalent = test[1]) != test[2]:
1077 print 'ERROR: none_if(%s) returned [%s], expected [%s]' % (test[0], none_if(test[0], test[1]), test[2])
1078
1079 return True
1080 #-----------------------------------------------------------------------
1082 tests = [
1083 [True, 'Yes', 'Yes', 'Yes'],
1084 [False, 'OK', 'not OK', 'not OK']
1085 ]
1086 for test in tests:
1087 if bool2str(test[0], test[1], test[2]) != test[3]:
1088 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])
1089
1090 return True
1091 #-----------------------------------------------------------------------
1093
1094 print bool2subst(True, 'True', 'False', 'is None')
1095 print bool2subst(False, 'True', 'False', 'is None')
1096 print bool2subst(None, 'True', 'False', 'is None')
1097 #-----------------------------------------------------------------------
1099 print get_unique_filename()
1100 print get_unique_filename(prefix='test-')
1101 print get_unique_filename(suffix='tst')
1102 print get_unique_filename(prefix='test-', suffix='tst')
1103 print get_unique_filename(tmp_dir='/home/ncq/Archiv/')
1104 #-----------------------------------------------------------------------
1106 print "testing size2str()"
1107 print "------------------"
1108 tests = [0, 1, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000, 1000000000000, 10000000000000]
1109 for test in tests:
1110 print size2str(test)
1111 #-----------------------------------------------------------------------
1113
1114 test = """
1115 second line\n
1116 3rd starts with tab \n
1117 4th with a space \n
1118
1119 6th
1120
1121 """
1122 print unwrap(text = test, max_length = 25)
1123 #-----------------------------------------------------------------------
1125 test = 'line 1\nline 2\nline 3'
1126
1127 print "wrap 5-6-7 initial 0, subsequent 0"
1128 print wrap(test, 5)
1129 print
1130 print wrap(test, 6)
1131 print
1132 print wrap(test, 7)
1133 print "-------"
1134 raw_input()
1135 print "wrap 5 initial 1-1-3, subsequent 1-3-1"
1136 print wrap(test, 5, u' ', u' ')
1137 print
1138 print wrap(test, 5, u' ', u' ')
1139 print
1140 print wrap(test, 5, u' ', u' ')
1141 print "-------"
1142 raw_input()
1143 print "wrap 6 initial 1-1-3, subsequent 1-3-1"
1144 print wrap(test, 6, u' ', u' ')
1145 print
1146 print wrap(test, 6, u' ', u' ')
1147 print
1148 print wrap(test, 6, u' ', u' ')
1149 print "-------"
1150 raw_input()
1151 print "wrap 7 initial 1-1-3, subsequent 1-3-1"
1152 print wrap(test, 7, u' ', u' ')
1153 print
1154 print wrap(test, 7, u' ', u' ')
1155 print
1156 print wrap(test, 7, u' ', u' ')
1157 #-----------------------------------------------------------------------
1159 print '%s: %s' % (sys.argv[2], file2md5(sys.argv[2]))
1160 #-----------------------------------------------------------------------
1162 print u_link_symbol * 10
1163 #-----------------------------------------------------------------------
1165 print xml_escape_string(u'<')
1166 print xml_escape_string(u'>')
1167 print xml_escape_string(u'&')
1168 #-----------------------------------------------------------------------
1170 tests = [u'\\', u'^', u'~', u'{', u'}', u'%', u'&', u'#', u'$', u'_', u_euro]
1171 tests.append(u' '.join(tests))
1172 for test in tests:
1173 print u'%s:' % test, tex_escape_string(test)
1174 #-----------------------------------------------------------------------
1176 fname = gpg_decrypt_file(filename = sys.argv[2], passphrase = sys.argv[3])
1177 if fname is not None:
1178 print "successfully decrypted:", fname
1179 #-----------------------------------------------------------------------
1181 tests = [
1182 u'one line, no embedded line breaks ',
1183 u'one line\nwith embedded\nline\nbreaks\n '
1184 ]
1185 for test in tests:
1186 print 'as list:'
1187 print strip_trailing_empty_lines(text = test, eol=u'\n', return_list = True)
1188 print 'as string:'
1189 print u'>>>%s<<<' % strip_trailing_empty_lines(text = test, eol=u'\n', return_list = False)
1190 tests = [
1191 ['list', 'without', 'empty', 'trailing', 'lines'],
1192 ['list', 'with', 'empty', 'trailing', 'lines', '', ' ', '']
1193 ]
1194 for test in tests:
1195 print 'as list:'
1196 print strip_trailing_empty_lines(lines = test, eol = u'\n', return_list = True)
1197 print 'as string:'
1198 print strip_trailing_empty_lines(lines = test, eol = u'\n', return_list = False)
1199 #-----------------------------------------------------------------------
1201 tests = [
1202 r'abc.exe',
1203 r'\abc.exe',
1204 r'c:\abc.exe',
1205 r'c:\d\abc.exe',
1206 r'/home/ncq/tmp.txt',
1207 r'~/tmp.txt',
1208 r'./tmp.txt',
1209 r'./.././tmp.txt',
1210 r'tmp.txt'
1211 ]
1212 for t in tests:
1213 print "[%s] -> [%s]" % (t, fname_stem(t))
1214 #-----------------------------------------------------------------------
1215 #test_coalesce()
1216 #test_capitalize()
1217 #test_import_module()
1218 #test_mkdir()
1219 #test_gmPaths()
1220 #test_none_if()
1221 #test_bool2str()
1222 #test_bool2subst()
1223 #test_get_unique_filename()
1224 #test_size2str()
1225 #test_wrap()
1226 #test_input2decimal()
1227 #test_input2int()
1228 #test_unwrap()
1229 #test_md5()
1230 #test_unicode()
1231 #test_xml_escape()
1232 #test_gpg_decrypt()
1233 #test_strip_trailing_empty_lines()
1234 test_fname_stem()
1235 #test_tex_escape()
1236
1237 #===========================================================================
1238
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Fri Jul 12 03:56:29 2013 | http://epydoc.sourceforge.net |