| Home | Trees | Indices | Help |
|
|---|
|
|
1 # -*- coding: utf-8 -*-
2
3
4
5 __doc__ = """GNUmed general tools."""
6
7 #===========================================================================
8 __author__ = "K. Hilbert <Karsten.Hilbert@gmx.net>"
9 __license__ = "GPL v2 or later (details at http://www.gnu.org)"
10
11 # std libs
12 import sys
13 import os
14 import os.path
15 import csv
16 import tempfile
17 import logging
18 import hashlib
19 import platform
20 import subprocess
21 import decimal
22 import getpass
23 import io
24 import functools
25 import json
26 import shutil
27 import zipfile
28 import datetime as pydt
29 import re as regex
30 import xml.sax.saxutils as xml_tools
31 # old:
32 import pickle, zlib
33
34
35 # GNUmed libs
36 if __name__ == '__main__':
37 sys.path.insert(0, '../../')
38
39
40 from Gnumed.pycommon import gmBorg
41
42
43 _log = logging.getLogger('gm.tools')
44
45 # CAPitalization modes:
46 ( CAPS_NONE, # don't touch it
47 CAPS_FIRST, # CAP first char, leave rest as is
48 CAPS_ALLCAPS, # CAP all chars
49 CAPS_WORDS, # CAP first char of every word
50 CAPS_NAMES, # CAP in a way suitable for names (tries to be smart)
51 CAPS_FIRST_ONLY # CAP first char, lowercase the rest
52 ) = range(6)
53
54
55 u_currency_pound = '\u00A3' # Pound sign
56 u_currency_sign = '\u00A4' # generic currency sign
57 u_currency_yen = '\u00A5' # Yen sign
58 u_right_double_angle_quote = '\u00AB' # <<
59 u_registered_trademark = '\u00AE'
60 u_plus_minus = '\u00B1'
61 u_superscript_one = '\u00B9' # ^1
62 u_left_double_angle_quote = '\u00BB' # >>
63 u_one_quarter = '\u00BC'
64 u_one_half = '\u00BD'
65 u_three_quarters = '\u00BE'
66 u_multiply = '\u00D7' # x
67 u_greek_ALPHA = '\u0391'
68 u_greek_alpha = '\u03b1'
69 u_greek_OMEGA = '\u03A9'
70 u_greek_omega = '\u03c9'
71 u_dagger = '\u2020'
72 u_triangular_bullet = '\u2023' # triangular bullet (>)
73 u_ellipsis = '\u2026' # ...
74 u_euro = '\u20AC' # EURO sign
75 u_numero = '\u2116' # No. / # sign
76 u_down_left_arrow = '\u21B5' # <-'
77 u_left_arrow = '\u2190' # <--
78 u_up_arrow = '\u2191'
79 u_arrow2right = '\u2192' # -->
80 u_down_arrow = '\u2193'
81 u_left_arrow_with_tail = '\u21a2' # <--<
82 u_arrow2right_from_bar = '\u21a6' # |->
83 u_arrow2right_until_vertical_bar = '\u21e5' # -->|
84 u_sum = '\u2211' # sigma
85 u_almost_equal_to = '\u2248' # approximately / nearly / roughly
86 u_corresponds_to = '\u2258'
87 u_infinity = '\u221E'
88 u_arrow2right_until_vertical_bar2 = '\u2b72' # -->|
89 u_diameter = '\u2300'
90 u_checkmark_crossed_out = '\u237B'
91 u_box_vert_left = '\u23b8'
92 u_box_vert_right = '\u23b9'
93 u_box_horiz_single = '\u2500' # -
94 u_box_vert_light = '\u2502'
95 u_box_horiz_light_3dashes = '\u2504' # ...
96 u_box_vert_light_4dashes = '\u2506'
97 u_box_horiz_4dashes = '\u2508' # ....
98 u_box_T_right = '\u251c' # |-
99 u_box_T_left = '\u2524' # -|
100 u_box_T_down = '\u252c'
101 u_box_T_up = '\u2534'
102 u_box_plus = '\u253c'
103 u_box_top_double = '\u2550'
104 u_box_top_left_double_single = '\u2552'
105 u_box_top_right_double_single = '\u2555'
106 u_box_top_left_arc = '\u256d'
107 u_box_top_right_arc = '\u256e'
108 u_box_bottom_right_arc = '\u256f'
109 u_box_bottom_left_arc = '\u2570'
110 u_box_horiz_light_heavy = '\u257c'
111 u_box_horiz_heavy_light = '\u257e'
112 u_skull_and_crossbones = '\u2620'
113 u_caduceus = '\u2624'
114 u_frowning_face = '\u2639'
115 u_smiling_face = '\u263a'
116 u_black_heart = '\u2665'
117 u_female = '\u2640'
118 u_male = '\u2642'
119 u_male_female = '\u26a5'
120 u_checkmark_thin = '\u2713'
121 u_checkmark_thick = '\u2714'
122 u_heavy_greek_cross = '\u271a'
123 u_arrow2right_thick = '\u2794'
124 u_writing_hand = '\u270d'
125 u_pencil_1 = '\u270e'
126 u_pencil_2 = '\u270f'
127 u_pencil_3 = '\u2710'
128 u_latin_cross = '\u271d'
129 u_arrow2right_until_black_diamond = '\u291e' # ->*
130 u_kanji_yen = '\u5186' # Yen kanji
131 u_replacement_character = '\ufffd'
132 u_link_symbol = '\u1f517'
133
134 _kB = 1024
135 _MB = 1024 * _kB
136 _GB = 1024 * _MB
137 _TB = 1024 * _GB
138 _PB = 1024 * _TB
139
140 #===========================================================================
142
143 print(".========================================================")
144 print("| Unhandled exception caught !")
145 print("| Type :", t)
146 print("| Value:", v)
147 print("`========================================================")
148 _log.critical('unhandled exception caught', exc_info = (t,v,tb))
149 sys.__excepthook__(t,v,tb)
150
151 #===========================================================================
152 # path level operations
153 #---------------------------------------------------------------------------
155 try:
156 if mode is None:
157 os.makedirs(directory)
158 else:
159 old_umask = os.umask(0)
160 os.makedirs(directory, mode)
161 os.umask(old_umask)
162 except OSError as e:
163 if (e.errno == 17) and not os.path.isdir(directory):
164 raise
165 return True
166
167 #---------------------------------------------------------------------------
169 #-------------------------------
170 def _on_rm_error(func, path, exc):
171 _log.error('error while shutil.rmtree(%s)', path, exc_info=exc)
172 return True
173 #-------------------------------
174 error_count = 0
175 try:
176 shutil.rmtree(directory, False, _on_rm_error)
177 except Exception:
178 _log.exception('cannot shutil.rmtree(%s)', directory)
179 error_count += 1
180 return error_count
181
182 #---------------------------------------------------------------------------
184 _log.debug('cleaning out [%s]', directory)
185 try:
186 items = os.listdir(directory)
187 except OSError:
188 return False
189 for item in items:
190 # attempt file/link removal and ignore (but log) errors
191 full_item = os.path.join(directory, item)
192 try:
193 os.remove(full_item)
194 except OSError: # as per the docs, this is a directory
195 _log.debug('[%s] seems to be a subdirectory', full_item)
196 errors = rmdir(full_item)
197 if errors > 0:
198 return False
199 except Exception:
200 _log.exception('cannot os.remove(%s) [a file or a link]', full_item)
201 return False
202
203 return True
204
205 #---------------------------------------------------------------------------
207 if prefix is None:
208 if base_dir is None:
209 prefix = 'sandbox-'
210 else:
211 prefix = 'gm_sandbox-'
212 return tempfile.mkdtemp (
213 prefix = prefix,
214 suffix = '',
215 dir = base_dir
216 )
217
218 #---------------------------------------------------------------------------
221
222 #---------------------------------------------------------------------------
224 # /home/user/dir/ -> dir
225 # /home/user/dir -> dir
226 return os.path.basename(os.path.normpath(directory)) # normpath removes trailing slashes if any
227
228 #---------------------------------------------------------------------------
230 try:
231 return len(os.listdir(directory)) == 0
232 except OSError as exc:
233 if exc.errno == 2:
234 return None
235 raise
236
237 #---------------------------------------------------------------------------
239 """This class provides the following paths:
240
241 .home_dir user home
242 .local_base_dir script installation dir
243 .working_dir current dir
244 .user_config_dir
245 .system_config_dir
246 .system_app_data_dir (not writable)
247 .tmp_dir instance-local
248 .user_tmp_dir user-local (NOT per instance)
249 """
251 """Setup pathes.
252
253 <app_name> will default to (name of the script - .py)
254 """
255 try:
256 self.already_inited
257 return
258 except AttributeError:
259 pass
260
261 self.init_paths(app_name=app_name, wx=wx)
262 self.already_inited = True
263 #--------------------------------------
264 # public API
265 #--------------------------------------
267
268 if wx is None:
269 _log.debug('wxPython not available')
270 _log.debug('detecting paths directly')
271
272 if app_name is None:
273 app_name, ext = os.path.splitext(os.path.basename(sys.argv[0]))
274 _log.info('app name detected as [%s]', app_name)
275 else:
276 _log.info('app name passed in as [%s]', app_name)
277
278 # the user home, doesn't work in Wine so work around that
279 self.__home_dir = None
280
281 # where the main script (the "binary") is installed
282 if getattr(sys, 'frozen', False):
283 _log.info('frozen app, installed into temporary path')
284 # this would find the path of *THIS* file
285 #self.local_base_dir = os.path.dirname(__file__)
286 # while this is documented on the web, the ${_MEIPASS2} does not exist
287 #self.local_base_dir = os.environ.get('_MEIPASS2')
288 # this is what Martin Zibricky <mzibr.public@gmail.com> told us to use
289 # when asking about this on pyinstaller@googlegroups.com
290 #self.local_base_dir = sys._MEIPASS
291 # however, we are --onedir, so we should look at sys.executable
292 # as per the pyinstaller manual
293 self.local_base_dir = os.path.dirname(sys.executable)
294 else:
295 self.local_base_dir = os.path.abspath(os.path.dirname(sys.argv[0]))
296
297 # the current working dir at the OS
298 self.working_dir = os.path.abspath(os.curdir)
299
300 # user-specific config dir, usually below the home dir
301 mkdir(os.path.join(self.home_dir, '.%s' % app_name))
302 self.user_config_dir = os.path.join(self.home_dir, '.%s' % app_name)
303
304 # system-wide config dir, usually below /etc/ under UN*X
305 try:
306 self.system_config_dir = os.path.join('/etc', app_name)
307 except ValueError:
308 #self.system_config_dir = self.local_base_dir
309 self.system_config_dir = self.user_config_dir
310
311 # system-wide application data dir
312 try:
313 self.system_app_data_dir = os.path.join(sys.prefix, 'share', app_name)
314 except ValueError:
315 self.system_app_data_dir = self.local_base_dir
316
317 # temporary directory
318 try:
319 self.__tmp_dir_already_set
320 _log.debug('temp dir already set')
321 except AttributeError:
322 _log.info('initial (user level) temp dir: %s', tempfile.gettempdir())
323 # $TMP/gnumed-$USER/
324 self.user_tmp_dir = os.path.join(tempfile.gettempdir(), app_name + r'-' + getpass.getuser())
325 mkdir(self.user_tmp_dir, 0o700)
326 tempfile.tempdir = self.user_tmp_dir
327 _log.info('intermediate (app level) temp dir: %s', tempfile.gettempdir())
328 # $TMP/gnumed-$USER/g$UNIQUE/
329 self.tmp_dir = tempfile.mkdtemp(prefix = r'g')
330 _log.info('final (app instance level) temp dir: %s', tempfile.gettempdir())
331
332 self.__log_paths()
333 if wx is None:
334 return True
335
336 # retry with wxPython
337 _log.debug('re-detecting paths with wxPython')
338
339 std_paths = wx.StandardPaths.Get()
340 _log.info('wxPython app name is [%s]', wx.GetApp().GetAppName())
341
342 # user-specific config dir, usually below the home dir
343 mkdir(os.path.join(std_paths.GetUserConfigDir(), '.%s' % app_name))
344 self.user_config_dir = os.path.join(std_paths.GetUserConfigDir(), '.%s' % app_name)
345
346 # system-wide config dir, usually below /etc/ under UN*X
347 try:
348 tmp = std_paths.GetConfigDir()
349 if not tmp.endswith(app_name):
350 tmp = os.path.join(tmp, app_name)
351 self.system_config_dir = tmp
352 except ValueError:
353 # leave it at what it was from direct detection
354 pass
355
356 # system-wide application data dir
357 # Robin attests that the following doesn't always
358 # give sane values on Windows, so IFDEF it
359 if 'wxMSW' in wx.PlatformInfo:
360 _log.warning('this platform (wxMSW) sometimes returns a broken value for the system-wide application data dir')
361 else:
362 try:
363 self.system_app_data_dir = std_paths.GetDataDir()
364 except ValueError:
365 pass
366
367 self.__log_paths()
368 return True
369 #--------------------------------------
371 _log.debug('sys.argv[0]: %s', sys.argv[0])
372 _log.debug('sys.executable: %s', sys.executable)
373 _log.debug('sys._MEIPASS: %s', getattr(sys, '_MEIPASS', '<not found>'))
374 _log.debug('os.environ["_MEIPASS2"]: %s', os.environ.get('_MEIPASS2', '<not found>'))
375 _log.debug('__file__ : %s', __file__)
376 _log.debug('local application base dir: %s', self.local_base_dir)
377 _log.debug('current working dir: %s', self.working_dir)
378 _log.debug('user home dir: %s', self.home_dir)
379 _log.debug('user-specific config dir: %s', self.user_config_dir)
380 _log.debug('system-wide config dir: %s', self.system_config_dir)
381 _log.debug('system-wide application data dir: %s', self.system_app_data_dir)
382 _log.debug('temporary dir (user): %s', self.user_tmp_dir)
383 _log.debug('temporary dir (instance): %s', self.tmp_dir)
384
385 #--------------------------------------
386 # properties
387 #--------------------------------------
389 if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)):
390 msg = '[%s:user_config_dir]: invalid path [%s]' % (self.__class__.__name__, path)
391 _log.error(msg)
392 raise ValueError(msg)
393 self.__user_config_dir = path
394
397
398 user_config_dir = property(_get_user_config_dir, _set_user_config_dir)
399 #--------------------------------------
401 if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)):
402 msg = '[%s:system_config_dir]: invalid path [%s]' % (self.__class__.__name__, path)
403 _log.error(msg)
404 raise ValueError(msg)
405 self.__system_config_dir = path
406
409
410 system_config_dir = property(_get_system_config_dir, _set_system_config_dir)
411 #--------------------------------------
413 if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)):
414 msg = '[%s:system_app_data_dir]: invalid path [%s]' % (self.__class__.__name__, path)
415 _log.error(msg)
416 raise ValueError(msg)
417 self.__system_app_data_dir = path
418
421
422 system_app_data_dir = property(_get_system_app_data_dir, _set_system_app_data_dir)
423 #--------------------------------------
426
428 if self.__home_dir is not None:
429 return self.__home_dir
430
431 tmp = os.path.expanduser('~')
432 if tmp == '~':
433 _log.error('this platform does not expand ~ properly')
434 try:
435 tmp = os.environ['USERPROFILE']
436 except KeyError:
437 _log.error('cannot access $USERPROFILE in environment')
438
439 if not (
440 os.access(tmp, os.R_OK)
441 and
442 os.access(tmp, os.X_OK)
443 and
444 os.access(tmp, os.W_OK)
445 ):
446 msg = '[%s:home_dir]: invalid path [%s]' % (self.__class__.__name__, tmp)
447 _log.error(msg)
448 raise ValueError(msg)
449
450 self.__home_dir = tmp
451 return self.__home_dir
452
453 home_dir = property(_get_home_dir, _set_home_dir)
454 #--------------------------------------
456 if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)):
457 msg = '[%s:tmp_dir]: invalid path [%s]' % (self.__class__.__name__, path)
458 _log.error(msg)
459 raise ValueError(msg)
460 _log.debug('previous temp dir: %s', tempfile.gettempdir())
461 self.__tmp_dir = path
462 tempfile.tempdir = self.__tmp_dir
463 _log.debug('new temp dir: %s', tempfile.gettempdir())
464 self.__tmp_dir_already_set = True
465
468
469 tmp_dir = property(_get_tmp_dir, _set_tmp_dir)
470
471 #===========================================================================
472 # file related tools
473 #---------------------------------------------------------------------------
474 -def recode_file(source_file=None, target_file=None, source_encoding='utf8', target_encoding=None, base_dir=None, error_mode='replace'):
475 if target_encoding is None:
476 return source_file
477 if target_encoding == source_encoding:
478 return source_file
479 if target_file is None:
480 target_file = get_unique_filename (
481 prefix = '%s-%s_%s-' % (fname_stem(source_file), source_encoding, target_encoding),
482 suffix = fname_extension(source_file, '.txt'),
483 tmp_dir = base_dir
484 )
485
486 _log.debug('[%s] -> [%s] (%s -> %s)', source_encoding, target_encoding, source_file, target_file)
487
488 in_file = io.open(source_file, mode = 'rt', encoding = source_encoding)
489 out_file = io.open(target_file, mode = 'wt', encoding = target_encoding, errors = error_mode)
490 for line in in_file:
491 out_file.write(line)
492 out_file.close()
493 in_file.close()
494
495 return target_file
496
497 #---------------------------------------------------------------------------
499 _log.debug('unzipping [%s] -> [%s]', archive_name, target_dir)
500 success = False
501 try:
502 with zipfile.ZipFile(archive_name) as archive:
503 archive.extractall(target_dir)
504 success = True
505 except Exception:
506 _log.exception('cannot unzip')
507 return False
508 if remove_archive:
509 remove_file(archive_name)
510 return success
511
512 #---------------------------------------------------------------------------
514 # attempt file remove and ignore (but log) errors
515 try:
516 os.remove(filename)
517 except Exception:
518 if log_error:
519 _log.exception('cannot os.remove(%s)', filename)
520 return False
521
522 return True
523
524 #---------------------------------------------------------------------------
526
527 if platform.system() == 'Windows':
528 exec_name = 'gpg.exe'
529 else:
530 exec_name = 'gpg'
531
532 tmp, fname = os.path.split(filename)
533 basename, tmp = os.path.splitext(fname)
534 filename_decrypted = get_unique_filename(prefix = '%s-decrypted-' % basename)
535
536 args = [exec_name, '--verbose', '--batch', '--yes', '--passphrase-fd', '0', '--output', filename_decrypted, '--decrypt', filename]
537 _log.debug('GnuPG args: %s' % str(args))
538
539 try:
540 gpg = subprocess.Popen (
541 args = args,
542 stdin = subprocess.PIPE,
543 stdout = subprocess.PIPE,
544 stderr = subprocess.PIPE,
545 close_fds = False
546 )
547 except (OSError, ValueError, subprocess.CalledProcessError):
548 _log.exception('there was a problem executing gpg')
549 gmDispatcher.send(signal = 'statustext', msg = _('Error running GnuPG. Cannot decrypt data.'), beep = True)
550 return
551
552 out, error = gpg.communicate(passphrase)
553 _log.debug('gpg returned [%s]', gpg.returncode)
554 if gpg.returncode != 0:
555 _log.debug('GnuPG STDOUT:\n%s', out)
556 _log.debug('GnuPG STDERR:\n%s', error)
557 return None
558
559 return filename_decrypted
560
561 #---------------------------------------------------------------------------
563 blocksize = 2**10 * 128 # 128k, since md5 uses 128 byte blocks
564 _log.debug('md5(%s): <%s> byte blocks', filename, blocksize)
565
566 f = io.open(filename, mode = 'rb')
567
568 md5 = hashlib.md5()
569 while True:
570 data = f.read(blocksize)
571 if not data:
572 break
573 md5.update(data)
574 f.close()
575
576 _log.debug('md5(%s): %s', filename, md5.hexdigest())
577
578 if return_hex:
579 return md5.hexdigest()
580 return md5.digest()
581
582 #---------------------------------------------------------------------------
584 _log.debug('chunked_md5(%s, chunk_size=%s bytes)', filename, chunk_size)
585 md5_concat = ''
586 f = open(filename, 'rb')
587 while True:
588 md5 = hashlib.md5()
589 data = f.read(chunk_size)
590 if not data:
591 break
592 md5.update(data)
593 md5_concat += md5.hexdigest()
594 f.close()
595 md5 = hashlib.md5()
596 md5.update(md5_concat)
597 hex_digest = md5.hexdigest()
598 _log.debug('md5("%s"): %s', md5_concat, hex_digest)
599 return hex_digest
600
601 #---------------------------------------------------------------------------
605
606 #def utf_8_encoder(unicode_csv_data):
607 # for line in unicode_csv_data:
608 # yield line.encode('utf-8')
609
610 default_csv_reader_rest_key = 'list_of_values_of_unknown_fields'
611
613
614 # csv.py doesn't do Unicode; encode temporarily as UTF-8:
615 try:
616 is_dict_reader = kwargs['dict']
617 del kwargs['dict']
618 if is_dict_reader is not True:
619 raise KeyError
620 kwargs['restkey'] = default_csv_reader_rest_key
621 csv_reader = csv.DictReader(unicode2charset_encoder(unicode_csv_data), dialect=dialect, **kwargs)
622 except KeyError:
623 is_dict_reader = False
624 csv_reader = csv.reader(unicode2charset_encoder(unicode_csv_data), dialect=dialect, **kwargs)
625
626 for row in csv_reader:
627 # decode ENCODING back to Unicode, cell by cell:
628 if is_dict_reader:
629 for key in row.keys():
630 if key == default_csv_reader_rest_key:
631 old_data = row[key]
632 new_data = []
633 for val in old_data:
634 new_data.append(str(val, encoding))
635 row[key] = new_data
636 if default_csv_reader_rest_key not in csv_reader.fieldnames:
637 csv_reader.fieldnames.append(default_csv_reader_rest_key)
638 else:
639 row[key] = str(row[key], encoding)
640 yield row
641 else:
642 yield [ str(cell, encoding) for cell in row ]
643 #yield [str(cell, 'utf-8') for cell in row]
644
645 #---------------------------------------------------------------------------
647 """Normalizes unicode, removes non-alpha characters, converts spaces to underscores."""
648
649 dir_part, name_part = os.path.split(filename)
650 if name_part == '':
651 return filename
652
653 import unicodedata
654 name_part = unicodedata.normalize('NFKD', name_part)
655 # remove everything not in group []
656 name_part = regex.sub (
657 '[^.\w\s[\]()%§+-]',
658 '',
659 name_part,
660 flags = regex.UNICODE
661 ).strip()
662 # translate whitespace to underscore
663 name_part = regex.sub (
664 '\s+',
665 '_',
666 name_part,
667 flags = regex.UNICODE
668 )
669 return os.path.join(dir_part, name_part)
670
671 #---------------------------------------------------------------------------
673 """/home/user/dir/filename.ext -> filename"""
674 return os.path.splitext(os.path.basename(filename))[0]
675
676 #---------------------------------------------------------------------------
678 """/home/user/dir/filename.ext -> /home/user/dir/filename"""
679 return os.path.splitext(filename)[0]
680
681 #---------------------------------------------------------------------------
683 """ /home/user/dir/filename.ext -> .ext
684 '' or '.' -> fallback if any else ''
685 """
686 ext = os.path.splitext(filename)[1]
687 if ext.strip() not in ['.', '']:
688 return ext
689 if fallback is None:
690 return ''
691 return fallback
692
693 #---------------------------------------------------------------------------
697
698 #---------------------------------------------------------------------------
702
703 #---------------------------------------------------------------------------
705 """This introduces a race condition between the file.close() and
706 actually using the filename.
707
708 The file will NOT exist after calling this function.
709 """
710 if tmp_dir is not None:
711 if (
712 not os.access(tmp_dir, os.F_OK)
713 or
714 not os.access(tmp_dir, os.X_OK | os.W_OK)
715 ):
716 _log.warning('cannot os.access() temporary dir [%s], using system default', tmp_dir)
717 tmp_dir = None
718
719 if include_timestamp:
720 ts = pydt.datetime.now().strftime('%m%d-%H%M%S-')
721 else:
722 ts = ''
723
724 kwargs = {
725 'dir': tmp_dir,
726 # make sure file gets deleted as soon as
727 # .close()d so we can "safely" open it again
728 'delete': True
729 }
730
731 if prefix is None:
732 kwargs['prefix'] = 'gmd-%s' % ts
733 else:
734 kwargs['prefix'] = prefix + ts
735
736 if suffix in [None, '']:
737 kwargs['suffix'] = '.tmp'
738 else:
739 if not suffix.startswith('.'):
740 suffix = '.' + suffix
741 kwargs['suffix'] = suffix
742
743 f = tempfile.NamedTemporaryFile(**kwargs)
744 filename = f.name
745 f.close()
746
747 return filename
748
749 #---------------------------------------------------------------------------
751 import ctypes
752 #windows_create_symlink = ctypes.windll.kernel32.CreateSymbolicLinkW
753 kernel32 = ctype.WinDLL('kernel32', use_last_error = True)
754 windows_create_symlink = kernel32.CreateSymbolicLinkW
755 windows_create_symlink.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_uint32)
756 windows_create_symlink.restype = ctypes.c_ubyte
757 if os.path.isdir(physical_name):
758 flags = 1
759 else:
760 flags = 0
761 ret_code = windows_create_symlink(link_name, physical_name.replace('/', '\\'), flags)
762 _log.debug('ctypes.windll.kernel32.CreateSymbolicLinkW() [%s] exit code: %s', windows_create_symlink, ret_code)
763 if ret_code == 0:
764 raise ctypes.WinError()
765 return ret_code
766
767 #---------------------------------------------------------------------------
769
770 _log.debug('creating symlink (overwrite = %s):', overwrite)
771 _log.debug('link [%s] =>', link_name)
772 _log.debug('=> physical [%s]', physical_name)
773
774 if os.path.exists(link_name):
775 _log.debug('link exists')
776 if overwrite:
777 return True
778 return False
779
780 try:
781 os.symlink(physical_name, link_name)
782 except (AttributeError, NotImplementedError):
783 _log.debug('this Python does not have os.symlink(), resorting to ctypes')
784 __make_symlink_on_windows(physical_name, link_name)
785 #except OSError:
786 # unpriviledged on Windows
787
788 return True
789
790 #===========================================================================
791 -def import_module_from_directory(module_path=None, module_name=None, always_remove_path=False):
792 """Import a module from any location."""
793
794 _log.debug('CWD: %s', os.getcwd())
795
796 remove_path = always_remove_path or False
797 if module_path not in sys.path:
798 _log.info('appending to sys.path: [%s]' % module_path)
799 sys.path.append(module_path)
800 remove_path = True
801
802 _log.debug('will remove import path: %s', remove_path)
803
804 if module_name.endswith('.py'):
805 module_name = module_name[:-3]
806
807 try:
808 module = __import__(module_name)
809 except Exception:
810 _log.exception('cannot __import__() module [%s] from [%s]' % (module_name, module_path))
811 while module_path in sys.path:
812 sys.path.remove(module_path)
813 raise
814
815 _log.info('imported module [%s] as [%s]' % (module_name, module))
816 if remove_path:
817 while module_path in sys.path:
818 sys.path.remove(module_path)
819
820 return module
821
822 #===========================================================================
823 # text related tools
824 #---------------------------------------------------------------------------
826 if size == 1:
827 return template % _('1 Byte')
828 if size < 10 * _kB:
829 return template % _('%s Bytes') % size
830 if size < _MB:
831 return template % '%.1f kB' % (float(size) / _kB)
832 if size < _GB:
833 return template % '%.1f MB' % (float(size) / _MB)
834 if size < _TB:
835 return template % '%.1f GB' % (float(size) / _GB)
836 if size < _PB:
837 return template % '%.1f TB' % (float(size) / _TB)
838 return template % '%.1f PB' % (float(size) / _PB)
839
840 #---------------------------------------------------------------------------
842 if boolean is None:
843 return none_return
844 if boolean:
845 return true_return
846 if not boolean:
847 return false_return
848 raise ValueError('bool2subst(): <boolean> arg must be either of True, False, None')
849
850 #---------------------------------------------------------------------------
852 return bool2subst (
853 boolean = bool(boolean),
854 true_return = true_str,
855 false_return = false_str
856 )
857
858 #---------------------------------------------------------------------------
860 """Modelled after the SQL NULLIF function."""
861 if value is None:
862 return None
863 if strip_string:
864 stripped = value.strip()
865 else:
866 stripped = value
867 if stripped == none_equivalent:
868 return None
869 return value
870
871 #---------------------------------------------------------------------------
872 -def coalesce(initial=None, instead=None, template_initial=None, template_instead=None, none_equivalents=None, function_initial=None):
873 """Modelled after the SQL coalesce function.
874
875 To be used to simplify constructs like:
876
877 if initial is None (or in none_equivalents):
878 real_value = (template_instead % instead) or instead
879 else:
880 real_value = (template_initial % initial) or initial
881 print real_value
882
883 @param initial: the value to be tested for <None>
884 @type initial: any Python type, must have a __str__ method if template_initial is not None
885 @param instead: the value to be returned if <initial> is None
886 @type instead: any Python type, must have a __str__ method if template_instead is not None
887 @param template_initial: if <initial> is returned replace the value into this template, must contain one <%s>
888 @type template_initial: string or None
889 @param template_instead: if <instead> is returned replace the value into this template, must contain one <%s>
890 @type template_instead: string or None
891
892 example:
893 function_initial = ('strftime', '%Y-%m-%d')
894
895 Ideas:
896 - list of insteads: initial, [instead, template], [instead, template], [instead, template], template_initial, ...
897 """
898 if none_equivalents is None:
899 none_equivalents = [None]
900
901 if initial in none_equivalents:
902
903 if template_instead is None:
904 return instead
905
906 return template_instead % instead
907
908 if function_initial is not None:
909 funcname, args = function_initial
910 func = getattr(initial, funcname)
911 initial = func(args)
912
913 if template_initial is None:
914 return initial
915
916 try:
917 return template_initial % initial
918 except TypeError:
919 return template_initial
920
921 #---------------------------------------------------------------------------
923 val = match_obj.group(0).lower()
924 if val in ['von', 'van', 'de', 'la', 'l', 'der', 'den']: # FIXME: this needs to expand, configurable ?
925 return val
926 buf = list(val)
927 buf[0] = buf[0].upper()
928 for part in ['mac', 'mc', 'de', 'la']:
929 if len(val) > len(part) and val[:len(part)] == part:
930 buf[len(part)] = buf[len(part)].upper()
931 return ''.join(buf)
932
933 #---------------------------------------------------------------------------
935 """Capitalize the first character but leave the rest alone.
936
937 Note that we must be careful about the locale, this may
938 have issues ! However, for UTF strings it should just work.
939 """
940 if (mode is None) or (mode == CAPS_NONE):
941 return text
942
943 if len(text) == 0:
944 return text
945
946 if mode == CAPS_FIRST:
947 if len(text) == 1:
948 return text[0].upper()
949 return text[0].upper() + text[1:]
950
951 if mode == CAPS_ALLCAPS:
952 return text.upper()
953
954 if mode == CAPS_FIRST_ONLY:
955 # if len(text) == 1:
956 # return text[0].upper()
957 return text[0].upper() + text[1:].lower()
958
959 if mode == CAPS_WORDS:
960 #return regex.sub(ur'(\w)(\w+)', lambda x: x.group(1).upper() + x.group(2).lower(), text)
961 return regex.sub(r'(\w)(\w+)', lambda x: x.group(1).upper() + x.group(2).lower(), text)
962
963 if mode == CAPS_NAMES:
964 #return regex.sub(r'\w+', __cap_name, text)
965 return capitalize(text=text, mode=CAPS_FIRST) # until fixed
966
967 print("ERROR: invalid capitalization mode: [%s], leaving input as is" % mode)
968 return text
969
970 #---------------------------------------------------------------------------
972
973 if isinstance(initial, decimal.Decimal):
974 return True, initial
975
976 val = initial
977
978 # float ? -> to string first
979 if type(val) == type(float(1.4)):
980 val = str(val)
981
982 # string ? -> "," to "."
983 if isinstance(val, str):
984 val = val.replace(',', '.', 1)
985 val = val.strip()
986
987 try:
988 d = decimal.Decimal(val)
989 return True, d
990 except (TypeError, decimal.InvalidOperation):
991 return False, val
992
993 #---------------------------------------------------------------------------
995
996 val = initial
997
998 # string ? -> "," to "."
999 if isinstance(val, str):
1000 val = val.replace(',', '.', 1)
1001 val = val.strip()
1002
1003 try:
1004 int_val = int(val)
1005 except (TypeError, ValueError):
1006 _log.exception('int(%s) failed', val)
1007 return False, initial
1008
1009 if minval is not None:
1010 if int_val < minval:
1011 _log.debug('%s < min (%s)', val, minval)
1012 return False, initial
1013 if maxval is not None:
1014 if int_val > maxval:
1015 _log.debug('%s > max (%s)', val, maxval)
1016 return False, initial
1017
1018 return True, int_val
1019
1020 #---------------------------------------------------------------------------
1022 if remove_repeats:
1023 if remove_whitespace:
1024 while text.lstrip().startswith(prefix):
1025 text = text.lstrip().replace(prefix, '', 1).lstrip()
1026 return text
1027 while text.startswith(prefix):
1028 text = text.replace(prefix, '', 1)
1029 return text
1030 if remove_whitespace:
1031 return text.lstrip().replace(prefix, '', 1).lstrip()
1032 return text.replace(prefix, '', 1)
1033
1034 #---------------------------------------------------------------------------
1036 suffix_len = len(suffix)
1037 if remove_repeats:
1038 if remove_whitespace:
1039 while text.rstrip().endswith(suffix):
1040 text = text.rstrip()[:-suffix_len].rstrip()
1041 return text
1042 while text.endswith(suffix):
1043 text = text[:-suffix_len]
1044 return text
1045 if remove_whitespace:
1046 return text.rstrip()[:-suffix_len].rstrip()
1047 return text[:-suffix_len]
1048
1049 #---------------------------------------------------------------------------
1051 if lines is None:
1052 lines = text.split(eol)
1053
1054 while True:
1055 if lines[0].strip(eol).strip() != '':
1056 break
1057 lines = lines[1:]
1058
1059 if return_list:
1060 return lines
1061
1062 return eol.join(lines)
1063
1064 #---------------------------------------------------------------------------
1066 if lines is None:
1067 lines = text.split(eol)
1068
1069 while True:
1070 if lines[-1].strip(eol).strip() != '':
1071 break
1072 lines = lines[:-1]
1073
1074 if return_list:
1075 return lines
1076
1077 return eol.join(lines)
1078
1079 #---------------------------------------------------------------------------
1081 return strip_trailing_empty_lines (
1082 lines = strip_leading_empty_lines(lines = lines, text = text, eol = eol, return_list = True),
1083 text = None,
1084 eol = eol,
1085 return_list = return_list
1086 )
1087
1088 #---------------------------------------------------------------------------
1089 -def list2text(lines, initial_indent='', subsequent_indent='', eol='\n', strip_leading_empty_lines=True, strip_trailing_empty_lines=True, strip_trailing_whitespace=True):
1090
1091 if len(lines) == 0:
1092 return ''
1093
1094 if strip_leading_empty_lines:
1095 lines = strip_leading_empty_lines(lines = lines, eol = eol, return_list = True)
1096
1097 if strip_trailing_empty_lines:
1098 lines = strip_trailing_empty_lines(lines = lines, eol = eol, return_list = True)
1099
1100 if strip_trailing_whitespace:
1101 lines = [ l.rstrip() for l in lines ]
1102
1103 indented_lines = [initial_indent + lines[0]]
1104 indented_lines.extend([ subsequent_indent + l for l in lines[1:] ])
1105
1106 return eol.join(indented_lines)
1107
1108 #---------------------------------------------------------------------------
1110 """A word-wrap function that preserves existing line breaks
1111 and most spaces in the text. Expects that existing line
1112 breaks are posix newlines (\n).
1113 """
1114 if width is None:
1115 return text
1116 wrapped = initial_indent + functools.reduce (
1117 lambda line, word, width=width: '%s%s%s' % (
1118 line,
1119 ' \n'[(len(line) - line.rfind('\n') - 1 + len(word.split('\n',1)[0]) >= width)],
1120 word
1121 ),
1122 text.split(' ')
1123 )
1124
1125 if subsequent_indent != '':
1126 wrapped = ('\n%s' % subsequent_indent).join(wrapped.split('\n'))
1127
1128 if eol != '\n':
1129 wrapped = wrapped.replace('\n', eol)
1130
1131 return wrapped
1132
1133 #---------------------------------------------------------------------------
1134 -def unwrap(text=None, max_length=None, strip_whitespace=True, remove_empty_lines=True, line_separator = ' // '):
1135
1136 text = text.replace('\r', '')
1137 lines = text.split('\n')
1138 text = ''
1139 for line in lines:
1140
1141 if strip_whitespace:
1142 line = line.strip().strip('\t').strip()
1143
1144 if remove_empty_lines:
1145 if line == '':
1146 continue
1147
1148 text += ('%s%s' % (line, line_separator))
1149
1150 text = text.rstrip(line_separator)
1151
1152 if max_length is not None:
1153 text = text[:max_length]
1154
1155 text = text.rstrip(line_separator)
1156
1157 return text
1158
1159 #---------------------------------------------------------------------------
1161
1162 if len(text) <= max_length:
1163 return text
1164
1165 return text[:max_length-1] + u_ellipsis
1166
1167 #---------------------------------------------------------------------------
1168 -def shorten_words_in_line(text=None, max_length=None, min_word_length=None, ignore_numbers=True, ellipsis=u_ellipsis):
1169 if text is None:
1170 return None
1171 if max_length is None:
1172 max_length = len(text)
1173 else:
1174 if len(text) <= max_length:
1175 return text
1176 old_words = regex.split('\s+', text, flags = regex.UNICODE)
1177 no_old_words = len(old_words)
1178 max_word_length = max(min_word_length, (max_length // no_old_words))
1179 words = []
1180 for word in old_words:
1181 if len(word) <= max_word_length:
1182 words.append(word)
1183 continue
1184 if ignore_numbers:
1185 tmp = word.replace('-', '').replace('+', '').replace('.', '').replace(',', '').replace('/', '').replace('&', '').replace('*', '')
1186 if tmp.isdigit():
1187 words.append(word)
1188 continue
1189 words.append(word[:max_word_length] + ellipsis)
1190 return ' '.join(words)
1191
1192 #---------------------------------------------------------------------------
1196
1197 #---------------------------------------------------------------------------
1198 -def tex_escape_string(text=None, replace_known_unicode=True, replace_eol=False, keep_visual_eol=False):
1199 """Check for special TeX characters and transform them.
1200
1201 replace_eol:
1202 replaces "\n" with "\\newline"
1203 keep_visual_eol:
1204 replaces "\n" with "\\newline \n" such that
1205 both LaTeX will know to place a line break
1206 at this point as well as the visual formatting
1207 is preserved in the LaTeX source (think multi-
1208 row table cells)
1209 """
1210 text = text.replace('\\', '\\textbackslash') # requires \usepackage{textcomp} in LaTeX source
1211 text = text.replace('^', '\\textasciicircum') # requires \usepackage{textcomp} in LaTeX source
1212 text = text.replace('~', '\\textasciitilde') # requires \usepackage{textcomp} in LaTeX source
1213
1214 text = text.replace('{', '\\{')
1215 text = text.replace('}', '\\}')
1216 text = text.replace('%', '\\%')
1217 text = text.replace('&', '\\&')
1218 text = text.replace('#', '\\#')
1219 text = text.replace('$', '\\$')
1220 text = text.replace('_', '\\_')
1221 if replace_eol:
1222 if keep_visual_eol:
1223 text = text.replace('\n', '\\newline \n')
1224 else:
1225 text = text.replace('\n', '\\newline ')
1226
1227 if replace_known_unicode:
1228 # this should NOT be replaced for Xe(La)Tex
1229 text = text.replace(u_euro, '\\EUR')
1230
1231 return text
1232
1233 #---------------------------------------------------------------------------
1235 # a web search did not reveal anything else for Xe(La)Tex
1236 # as opposed to LaTeX, except true unicode chars
1237 return tex_escape_string(text = text, replace_known_unicode = False)
1238
1239 #---------------------------------------------------------------------------
1240 __html_escape_table = {
1241 "&": "&",
1242 '"': """,
1243 "'": "'",
1244 ">": ">",
1245 "<": "<",
1246 }
1247
1249 text = ''.join(__html_escape_table.get(char, char) for char in text)
1250 if replace_eol:
1251 if keep_visual_eol:
1252 text = text.replace('\n', '<br>\n')
1253 else:
1254 text = text.replace('\n', '<br>')
1255 return text
1256
1257 #---------------------------------------------------------------------------
1260
1261 #---------------------------------------------------------------------------
1263 if isinstance(obj, pydt.datetime):
1264 return obj.isoformat()
1265 raise TypeError('cannot json_serialize(%s)' % type(obj))
1266
1267 #---------------------------------------------------------------------------
1268 #---------------------------------------------------------------------------
1270 _log.info('comparing dict-likes: %s[%s] vs %s[%s]', coalesce(title1, '', '"%s" '), type(d1), coalesce(title2, '', '"%s" '), type(d2))
1271 try:
1272 d1 = dict(d1)
1273 except TypeError:
1274 pass
1275 try:
1276 d2 = dict(d2)
1277 except TypeError:
1278 pass
1279 keys_d1 = frozenset(d1.keys())
1280 keys_d2 = frozenset(d2.keys())
1281 different = False
1282 if len(keys_d1) != len(keys_d2):
1283 _log.info('different number of keys: %s vs %s', len(keys_d1), len(keys_d2))
1284 different = True
1285 for key in keys_d1:
1286 if key in keys_d2:
1287 if type(d1[key]) != type(d2[key]):
1288 _log.info('%25.25s: type(dict1) = %s = >>>%s<<<' % (key, type(d1[key]), d1[key]))
1289 _log.info('%25.25s type(dict2) = %s = >>>%s<<<' % ('', type(d2[key]), d2[key]))
1290 different = True
1291 continue
1292 if d1[key] == d2[key]:
1293 _log.info('%25.25s: both = >>>%s<<<' % (key, d1[key]))
1294 else:
1295 _log.info('%25.25s: dict1 = >>>%s<<<' % (key, d1[key]))
1296 _log.info('%25.25s dict2 = >>>%s<<<' % ('', d2[key]))
1297 different = True
1298 else:
1299 _log.info('%25.25s: %50.50s | <MISSING>' % (key, '>>>%s<<<' % d1[key]))
1300 different = True
1301 for key in keys_d2:
1302 if key in keys_d1:
1303 continue
1304 _log.info('%25.25s: %50.50s | %.50s' % (key, '<MISSING>', '>>>%s<<<' % d2[key]))
1305 different = True
1306 if different:
1307 _log.info('dict-likes appear to be different from each other')
1308 return False
1309 _log.info('dict-likes appear equal to each other')
1310 return True
1311
1312 #---------------------------------------------------------------------------
1313 -def format_dict_likes_comparison(d1, d2, title_left=None, title_right=None, left_margin=0, key_delim=' || ', data_delim=' | ', missing_string='=/=', difference_indicator='! ', ignore_diff_in_keys=None):
1314
1315 _log.info('comparing dict-likes: %s[%s] vs %s[%s]', coalesce(title_left, '', '"%s" '), type(d1), coalesce(title_right, '', '"%s" '), type(d2))
1316 append_type = False
1317 if None not in [title_left, title_right]:
1318 append_type = True
1319 type_left = type(d1)
1320 type_right = type(d2)
1321 if title_left is None:
1322 title_left = '%s' % type_left
1323 if title_right is None:
1324 title_right = '%s' % type_right
1325
1326 try: d1 = dict(d1)
1327 except TypeError: pass
1328 try: d2 = dict(d2)
1329 except TypeError: pass
1330 keys_d1 = d1.keys()
1331 keys_d2 = d2.keys()
1332 data = {}
1333 for key in keys_d1:
1334 data[key] = [d1[key], ' ']
1335 if key in d2:
1336 data[key][1] = d2[key]
1337 for key in keys_d2:
1338 if key in keys_d1:
1339 continue
1340 data[key] = [' ', d2[key]]
1341 max1 = max([ len('%s' % k) for k in keys_d1 ])
1342 max2 = max([ len('%s' % k) for k in keys_d2 ])
1343 max_len = max(max1, max2, len(_('<type>')))
1344 max_key_len_str = '%' + '%s.%s' % (max_len, max_len) + 's'
1345 max1 = max([ len('%s' % d1[k]) for k in keys_d1 ])
1346 max2 = max([ len('%s' % d2[k]) for k in keys_d2 ])
1347 max_data_len = min(max(max1, max2), 100)
1348 max_data_len_str = '%' + '%s.%s' % (max_data_len, max_data_len) + 's'
1349 diff_indicator_len_str = '%' + '%s.%s' % (len(difference_indicator), len(difference_indicator)) + 's'
1350 line_template = (' ' * left_margin) + diff_indicator_len_str + max_key_len_str + key_delim + max_data_len_str + data_delim + '%s'
1351
1352 lines = []
1353 # debugging:
1354 #lines.append(u' (40 regular spaces)')
1355 #lines.append((u' ' * 40) + u"(u' ' * 40)")
1356 #lines.append((u'%40.40s' % u'') + u"(u'%40.40s' % u'')")
1357 #lines.append((u'%40.40s' % u' ') + u"(u'%40.40s' % u' ')")
1358 #lines.append((u'%40.40s' % u'.') + u"(u'%40.40s' % u'.')")
1359 #lines.append(line_template)
1360 lines.append(line_template % ('', '', title_left, title_right))
1361 if append_type:
1362 lines.append(line_template % ('', _('<type>'), type_left, type_right))
1363
1364 if ignore_diff_in_keys is None:
1365 ignore_diff_in_keys = []
1366
1367 for key in keys_d1:
1368 append_type = False
1369 txt_left_col = '%s' % d1[key]
1370 try:
1371 txt_right_col = '%s' % d2[key]
1372 if type(d1[key]) != type(d2[key]):
1373 append_type = True
1374 except KeyError:
1375 txt_right_col = missing_string
1376 lines.append(line_template % (
1377 bool2subst (
1378 ((txt_left_col == txt_right_col) or (key in ignore_diff_in_keys)),
1379 '',
1380 difference_indicator
1381 ),
1382 key,
1383 shorten_text(txt_left_col, max_data_len),
1384 shorten_text(txt_right_col, max_data_len)
1385 ))
1386 if append_type:
1387 lines.append(line_template % (
1388 '',
1389 _('<type>'),
1390 shorten_text('%s' % type(d1[key]), max_data_len),
1391 shorten_text('%s' % type(d2[key]), max_data_len)
1392 ))
1393
1394 for key in keys_d2:
1395 if key in keys_d1:
1396 continue
1397 lines.append(line_template % (
1398 bool2subst((key in ignore_diff_in_keys), '', difference_indicator),
1399 key,
1400 shorten_text(missing_string, max_data_len),
1401 shorten_text('%s' % d2[key], max_data_len)
1402 ))
1403
1404 return lines
1405
1406 #---------------------------------------------------------------------------
1407 -def format_dict_like(d, relevant_keys=None, template=None, missing_key_template='<[%(key)s] MISSING>', left_margin=0, tabular=False, value_delimiters=('>>>', '<<<'), eol='\n'):
1408 if template is not None:
1409 # all keys in template better exist in d
1410 try:
1411 return template % d
1412 except KeyError:
1413 # or else
1414 _log.exception('template contains %%()s key(s) which do not exist in dict')
1415 # try to extend dict <d> to contain all required keys,
1416 # for that to work <relevant_keys> better list all
1417 # keys used in <template>
1418 if relevant_keys is not None:
1419 for key in relevant_keys:
1420 try:
1421 d[key]
1422 except KeyError:
1423 d[key] = missing_key_template % {'key': key}
1424 return template % d
1425
1426 if relevant_keys is None:
1427 relevant_keys = d.keys()
1428 lines = []
1429 if value_delimiters is None:
1430 delim_left = ''
1431 delim_right = ''
1432 else:
1433 delim_left, delim_right = value_delimiters
1434 if tabular:
1435 max_len = max([ len('%s' % k) for k in relevant_keys ])
1436 max_len_str = '%s.%s' % (max_len, max_len)
1437 line_template = (' ' * left_margin) + '%' + max_len_str + ('s: %s%%s%s' % (delim_left, delim_right))
1438 else:
1439 line_template = (' ' * left_margin) + '%%s: %s%%s%s' % (delim_left, delim_right)
1440 for key in relevant_keys:
1441 try:
1442 lines.append(line_template % (key, d[key]))
1443 except KeyError:
1444 pass
1445 if eol is None:
1446 return lines
1447 return eol.join(lines)
1448
1449 #---------------------------------------------------------------------------
1451 for key in required_keys:
1452 try:
1453 d[key]
1454 except KeyError:
1455 if missing_key_template is None:
1456 d[key] = None
1457 else:
1458 d[key] = missing_key_template % {'key': key}
1459 return d
1460
1461 #---------------------------------------------------------------------------
1462 #---------------------------------------------------------------------------
1464 """Obtains entry from standard input.
1465
1466 prompt: Prompt text to display in standard output
1467 default: Default value (for user to press enter only)
1468 CTRL-C: aborts and returns None
1469 """
1470 if prompt is None:
1471 msg = '(CTRL-C aborts)'
1472 else:
1473 msg = '%s (CTRL-C aborts)' % prompt
1474
1475 if default is None:
1476 msg = msg + ': '
1477 else:
1478 msg = '%s [%s]: ' % (msg, default)
1479
1480 try:
1481 usr_input = input(msg)
1482 except KeyboardInterrupt:
1483 return None
1484
1485 if usr_input == '':
1486 return default
1487
1488 return usr_input
1489
1490 #===========================================================================
1491 # image handling tools
1492 #---------------------------------------------------------------------------
1493 # builtin (ugly but tried and true) fallback icon
1494 __icon_serpent = \
1495 """x\xdae\x8f\xb1\x0e\x83 \x10\x86w\x9f\xe2\x92\x1blb\xf2\x07\x96\xeaH:0\xd6\
1496 \xc1\x85\xd5\x98N5\xa5\xef?\xf5N\xd0\x8a\xdcA\xc2\xf7qw\x84\xdb\xfa\xb5\xcd\
1497 \xd4\xda;\xc9\x1a\xc8\xb6\xcd<\xb5\xa0\x85\x1e\xeb\xbc\xbc7b!\xf6\xdeHl\x1c\
1498 \x94\x073\xec<*\xf7\xbe\xf7\x99\x9d\xb21~\xe7.\xf5\x1f\x1c\xd3\xbdVlL\xc2\
1499 \xcf\xf8ye\xd0\x00\x90\x0etH \x84\x80B\xaa\x8a\x88\x85\xc4(U\x9d$\xfeR;\xc5J\
1500 \xa6\x01\xbbt9\xceR\xc8\x81e_$\x98\xb9\x9c\xa9\x8d,y\xa9t\xc8\xcf\x152\xe0x\
1501 \xe9$\xf5\x07\x95\x0cD\x95t:\xb1\x92\xae\x9cI\xa8~\x84\x1f\xe0\xa3ec"""
1502
1504
1505 paths = gmPaths(app_name = 'gnumed', wx = wx)
1506
1507 candidates = [
1508 os.path.join(paths.system_app_data_dir, 'bitmaps', 'gm_icon-serpent_and_gnu.png'),
1509 os.path.join(paths.local_base_dir, 'bitmaps', 'gm_icon-serpent_and_gnu.png'),
1510 os.path.join(paths.system_app_data_dir, 'bitmaps', 'serpent.png'),
1511 os.path.join(paths.local_base_dir, 'bitmaps', 'serpent.png')
1512 ]
1513
1514 found_as = None
1515 for candidate in candidates:
1516 try:
1517 open(candidate, 'r').close()
1518 found_as = candidate
1519 break
1520 except IOError:
1521 _log.debug('icon not found in [%s]', candidate)
1522
1523 if found_as is None:
1524 _log.warning('no icon file found, falling back to builtin (ugly) icon')
1525 icon_bmp_data = wx.BitmapFromXPMData(pickle.loads(zlib.decompress(__icon_serpent)))
1526 icon.CopyFromBitmap(icon_bmp_data)
1527 else:
1528 _log.debug('icon found in [%s]', found_as)
1529 icon = wx.Icon()
1530 try:
1531 icon.LoadFile(found_as, wx.BITMAP_TYPE_ANY) #_PNG
1532 except AttributeError:
1533 _log.exception("this platform doesn't support wx.Icon().LoadFile()")
1534
1535 return icon
1536
1537 #===========================================================================
1538 # main
1539 #---------------------------------------------------------------------------
1540 if __name__ == '__main__':
1541
1542 if len(sys.argv) < 2:
1543 sys.exit()
1544
1545 if sys.argv[1] != 'test':
1546 sys.exit()
1547
1548 # for testing:
1549 logging.basicConfig(level = logging.DEBUG)
1550 from Gnumed.pycommon import gmI18N
1551 gmI18N.activate_locale()
1552 gmI18N.install_domain()
1553
1554 #-----------------------------------------------------------------------
1556
1557 tests = [
1558 [None, False],
1559
1560 ['', False],
1561 [' 0 ', True, 0],
1562
1563 [0, True, 0],
1564 [0.0, True, 0],
1565 [.0, True, 0],
1566 ['0', True, 0],
1567 ['0.0', True, 0],
1568 ['0,0', True, 0],
1569 ['00.0', True, 0],
1570 ['.0', True, 0],
1571 [',0', True, 0],
1572
1573 [0.1, True, decimal.Decimal('0.1')],
1574 [.01, True, decimal.Decimal('0.01')],
1575 ['0.1', True, decimal.Decimal('0.1')],
1576 ['0,1', True, decimal.Decimal('0.1')],
1577 ['00.1', True, decimal.Decimal('0.1')],
1578 ['.1', True, decimal.Decimal('0.1')],
1579 [',1', True, decimal.Decimal('0.1')],
1580
1581 [1, True, 1],
1582 [1.0, True, 1],
1583 ['1', True, 1],
1584 ['1.', True, 1],
1585 ['1,', True, 1],
1586 ['1.0', True, 1],
1587 ['1,0', True, 1],
1588 ['01.0', True, 1],
1589 ['01,0', True, 1],
1590 [' 01, ', True, 1],
1591
1592 [decimal.Decimal('1.1'), True, decimal.Decimal('1.1')]
1593 ]
1594 for test in tests:
1595 conversion_worked, result = input2decimal(initial = test[0])
1596
1597 expected2work = test[1]
1598
1599 if conversion_worked:
1600 if expected2work:
1601 if result == test[2]:
1602 continue
1603 else:
1604 print("ERROR (conversion result wrong): >%s<, expected >%s<, got >%s<" % (test[0], test[2], result))
1605 else:
1606 print("ERROR (conversion worked but was expected to fail): >%s<, got >%s<" % (test[0], result))
1607 else:
1608 if not expected2work:
1609 continue
1610 else:
1611 print("ERROR (conversion failed but was expected to work): >%s<, expected >%s<" % (test[0], test[2]))
1612 #-----------------------------------------------------------------------
1617 #-----------------------------------------------------------------------
1619
1620 val = None
1621 print(val, coalesce(val, 'is None', 'is not None'))
1622 val = 1
1623 print(val, coalesce(val, 'is None', 'is not None'))
1624 return
1625
1626 import datetime as dt
1627 print(coalesce(initial = dt.datetime.now(), template_initial = '-- %s --', function_initial = ('strftime', '%Y-%m-%d')))
1628
1629 print('testing coalesce()')
1630 print("------------------")
1631 tests = [
1632 [None, 'something other than <None>', None, None, 'something other than <None>'],
1633 ['Captain', 'Mr.', '%s.'[:4], 'Mr.', 'Capt.'],
1634 ['value to test', 'test 3 failed', 'template with "%s" included', None, 'template with "value to test" included'],
1635 ['value to test', 'test 4 failed', 'template with value not included', None, 'template with value not included'],
1636 [None, 'initial value was None', 'template_initial: %s', None, 'initial value was None'],
1637 [None, 'initial value was None', 'template_initial: %%(abc)s', None, 'initial value was None']
1638 ]
1639 passed = True
1640 for test in tests:
1641 result = coalesce (
1642 initial = test[0],
1643 instead = test[1],
1644 template_initial = test[2],
1645 template_instead = test[3]
1646 )
1647 if result != test[4]:
1648 print("ERROR")
1649 print("coalesce: (%s, %s, %s, %s)" % (test[0], test[1], test[2], test[3]))
1650 print("expected:", test[4])
1651 print("received:", result)
1652 passed = False
1653
1654 if passed:
1655 print("passed")
1656 else:
1657 print("failed")
1658 return passed
1659 #-----------------------------------------------------------------------
1661 print('testing capitalize() ...')
1662 success = True
1663 pairs = [
1664 # [original, expected result, CAPS mode]
1665 ['Boot', 'Boot', CAPS_FIRST_ONLY],
1666 ['boot', 'Boot', CAPS_FIRST_ONLY],
1667 ['booT', 'Boot', CAPS_FIRST_ONLY],
1668 ['BoOt', 'Boot', CAPS_FIRST_ONLY],
1669 ['boots-Schau', 'Boots-Schau', CAPS_WORDS],
1670 ['boots-sChau', 'Boots-Schau', CAPS_WORDS],
1671 ['boot camp', 'Boot Camp', CAPS_WORDS],
1672 ['fahrner-Kampe', 'Fahrner-Kampe', CAPS_NAMES],
1673 ['häkkönen', 'Häkkönen', CAPS_NAMES],
1674 ['McBurney', 'McBurney', CAPS_NAMES],
1675 ['mcBurney', 'McBurney', CAPS_NAMES],
1676 ['blumberg', 'Blumberg', CAPS_NAMES],
1677 ['roVsing', 'RoVsing', CAPS_NAMES],
1678 ['Özdemir', 'Özdemir', CAPS_NAMES],
1679 ['özdemir', 'Özdemir', CAPS_NAMES],
1680 ]
1681 for pair in pairs:
1682 result = capitalize(pair[0], pair[2])
1683 if result != pair[1]:
1684 success = False
1685 print('ERROR (caps mode %s): "%s" -> "%s", expected "%s"' % (pair[2], pair[0], result, pair[1]))
1686
1687 if success:
1688 print("... SUCCESS")
1689
1690 return success
1691 #-----------------------------------------------------------------------
1693 print("testing import_module_from_directory()")
1694 path = sys.argv[1]
1695 name = sys.argv[2]
1696 try:
1697 mod = import_module_from_directory(module_path = path, module_name = name)
1698 except:
1699 print("module import failed, see log")
1700 return False
1701
1702 print("module import succeeded", mod)
1703 print(dir(mod))
1704 return True
1705 #-----------------------------------------------------------------------
1709 #-----------------------------------------------------------------------
1711 print("testing gmPaths()")
1712 print("-----------------")
1713 paths = gmPaths(wx=None, app_name='gnumed')
1714 print("user config dir:", paths.user_config_dir)
1715 print("system config dir:", paths.system_config_dir)
1716 print("local base dir:", paths.local_base_dir)
1717 print("system app data dir:", paths.system_app_data_dir)
1718 print("working directory :", paths.working_dir)
1719 print("temp directory :", paths.tmp_dir)
1720 #-----------------------------------------------------------------------
1722 print("testing none_if()")
1723 print("-----------------")
1724 tests = [
1725 [None, None, None],
1726 ['a', 'a', None],
1727 ['a', 'b', 'a'],
1728 ['a', None, 'a'],
1729 [None, 'a', None],
1730 [1, 1, None],
1731 [1, 2, 1],
1732 [1, None, 1],
1733 [None, 1, None]
1734 ]
1735
1736 for test in tests:
1737 if none_if(value = test[0], none_equivalent = test[1]) != test[2]:
1738 print('ERROR: none_if(%s) returned [%s], expected [%s]' % (test[0], none_if(test[0], test[1]), test[2]))
1739
1740 return True
1741 #-----------------------------------------------------------------------
1743 tests = [
1744 [True, 'Yes', 'Yes', 'Yes'],
1745 [False, 'OK', 'not OK', 'not OK']
1746 ]
1747 for test in tests:
1748 if bool2str(test[0], test[1], test[2]) != test[3]:
1749 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]))
1750
1751 return True
1752 #-----------------------------------------------------------------------
1754
1755 print(bool2subst(True, 'True', 'False', 'is None'))
1756 print(bool2subst(False, 'True', 'False', 'is None'))
1757 print(bool2subst(None, 'True', 'False', 'is None'))
1758 #-----------------------------------------------------------------------
1760 print(get_unique_filename())
1761 print(get_unique_filename(prefix='test-'))
1762 print(get_unique_filename(suffix='tst'))
1763 print(get_unique_filename(prefix='test-', suffix='tst'))
1764 print(get_unique_filename(tmp_dir='/home/ncq/Archiv/'))
1765 #-----------------------------------------------------------------------
1767 print("testing size2str()")
1768 print("------------------")
1769 tests = [0, 1, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000, 1000000000000, 10000000000000]
1770 for test in tests:
1771 print(size2str(test))
1772 #-----------------------------------------------------------------------
1774
1775 test = """
1776 second line\n
1777 3rd starts with tab \n
1778 4th with a space \n
1779
1780 6th
1781
1782 """
1783 print(unwrap(text = test, max_length = 25))
1784 #-----------------------------------------------------------------------
1786 test = 'line 1\nline 2\nline 3'
1787
1788 print("wrap 5-6-7 initial 0, subsequent 0")
1789 print(wrap(test, 5))
1790 print()
1791 print(wrap(test, 6))
1792 print()
1793 print(wrap(test, 7))
1794 print("-------")
1795 input()
1796 print("wrap 5 initial 1-1-3, subsequent 1-3-1")
1797 print(wrap(test, 5, ' ', ' '))
1798 print()
1799 print(wrap(test, 5, ' ', ' '))
1800 print()
1801 print(wrap(test, 5, ' ', ' '))
1802 print("-------")
1803 input()
1804 print("wrap 6 initial 1-1-3, subsequent 1-3-1")
1805 print(wrap(test, 6, ' ', ' '))
1806 print()
1807 print(wrap(test, 6, ' ', ' '))
1808 print()
1809 print(wrap(test, 6, ' ', ' '))
1810 print("-------")
1811 input()
1812 print("wrap 7 initial 1-1-3, subsequent 1-3-1")
1813 print(wrap(test, 7, ' ', ' '))
1814 print()
1815 print(wrap(test, 7, ' ', ' '))
1816 print()
1817 print(wrap(test, 7, ' ', ' '))
1818 #-----------------------------------------------------------------------
1820 print('md5 %s: %s' % (sys.argv[2], file2md5(sys.argv[2])))
1821 print('chunked md5 %s: %s' % (sys.argv[2], file2chunked_md5(sys.argv[2])))
1822 #-----------------------------------------------------------------------
1824 print(u_link_symbol * 10)
1825 #-----------------------------------------------------------------------
1827 print(xml_escape_string('<'))
1828 print(xml_escape_string('>'))
1829 print(xml_escape_string('&'))
1830 #-----------------------------------------------------------------------
1832 tests = ['\\', '^', '~', '{', '}', '%', '&', '#', '$', '_', u_euro, 'abc\ndef\n\n1234']
1833 tests.append(' '.join(tests))
1834 for test in tests:
1835 print('%s:' % test, tex_escape_string(test))
1836 #-----------------------------------------------------------------------
1838 fname = gpg_decrypt_file(filename = sys.argv[2], passphrase = sys.argv[3])
1839 if fname is not None:
1840 print("successfully decrypted:", fname)
1841 #-----------------------------------------------------------------------
1843 tests = [
1844 'one line, no embedded line breaks ',
1845 'one line\nwith embedded\nline\nbreaks\n '
1846 ]
1847 for test in tests:
1848 print('as list:')
1849 print(strip_trailing_empty_lines(text = test, eol='\n', return_list = True))
1850 print('as string:')
1851 print('>>>%s<<<' % strip_trailing_empty_lines(text = test, eol='\n', return_list = False))
1852 tests = [
1853 ['list', 'without', 'empty', 'trailing', 'lines'],
1854 ['list', 'with', 'empty', 'trailing', 'lines', '', ' ', '']
1855 ]
1856 for test in tests:
1857 print('as list:')
1858 print(strip_trailing_empty_lines(lines = test, eol = '\n', return_list = True))
1859 print('as string:')
1860 print(strip_trailing_empty_lines(lines = test, eol = '\n', return_list = False))
1861 #-----------------------------------------------------------------------
1863 tests = [
1864 r'abc.exe',
1865 r'\abc.exe',
1866 r'c:\abc.exe',
1867 r'c:\d\abc.exe',
1868 r'/home/ncq/tmp.txt',
1869 r'~/tmp.txt',
1870 r'./tmp.txt',
1871 r'./.././tmp.txt',
1872 r'tmp.txt'
1873 ]
1874 for t in tests:
1875 print("[%s] -> [%s]" % (t, fname_stem(t)))
1876 #-----------------------------------------------------------------------
1878 print(sys.argv[2], 'empty:', dir_is_empty(sys.argv[2]))
1879
1880 #-----------------------------------------------------------------------
1882 d1 = {}
1883 d2 = {}
1884 d1[1] = 1
1885 d1[2] = 2
1886 d1[3] = 3
1887 # 4
1888 d1[5] = 5
1889
1890 d2[1] = 1
1891 d2[2] = None
1892 # 3
1893 d2[4] = 4
1894
1895 #compare_dict_likes(d1, d2)
1896
1897 d1 = {1: 1, 2: 2}
1898 d2 = {1: 1, 2: 2}
1899
1900 #compare_dict_likes(d1, d2, 'same1', 'same2')
1901 print(format_dict_like(d1, tabular = False))
1902 print(format_dict_like(d1, tabular = True))
1903 #print(format_dict_like(d2))
1904
1905 #-----------------------------------------------------------------------
1907 d1 = {}
1908 d2 = {}
1909 d1[1] = 1
1910 d1[2] = 2
1911 d1[3] = 3
1912 # 4
1913 d1[5] = 5
1914
1915 d2[1] = 1
1916 d2[2] = None
1917 # 3
1918 d2[4] = 4
1919
1920 print('\n'.join(format_dict_likes_comparison(d1, d2, 'd1', 'd2')))
1921
1922 d1 = {1: 1, 2: 2}
1923 d2 = {1: 1, 2: 2}
1924
1925 print('\n'.join(format_dict_likes_comparison(d1, d2, 'd1', 'd2')))
1926
1927 #-----------------------------------------------------------------------
1929 rmdir('cx:\windows\system3__2xxxxxxxxxxxxx')
1930
1931 #-----------------------------------------------------------------------
1933 #print(rm_dir_content('cx:\windows\system3__2xxxxxxxxxxxxx'))
1934 print(rm_dir_content('/tmp/user/1000/tmp'))
1935
1936 #-----------------------------------------------------------------------
1938 tests = [
1939 ('', '', ''),
1940 ('a', 'a', ''),
1941 ('\.br\MICROCYTES+1\.br\SPHEROCYTES present\.br\POLYCHROMASIAmoderate\.br\\', '\.br\\', 'MICROCYTES+1\.br\SPHEROCYTES present\.br\POLYCHROMASIAmoderate\.br\\')
1942 ]
1943 for test in tests:
1944 text, prefix, expect = test
1945 result = strip_prefix(text, prefix)
1946 if result == expect:
1947 continue
1948 print('test failed:', test)
1949 print('result:', result)
1950 #-----------------------------------------------------------------------
1952 tst = [
1953 ('123', 1),
1954 ('123', 2),
1955 ('123', 3),
1956 ('123', 4),
1957 ('', 1),
1958 ('1', 1),
1959 ('12', 1),
1960 ('', 2),
1961 ('1', 2),
1962 ('12', 2),
1963 ('123', 2)
1964 ]
1965 for txt, lng in tst:
1966 print('max', lng, 'of', txt, '=', shorten_text(txt, lng))
1967 #-----------------------------------------------------------------------
1969 tests = [
1970 '/tmp/test.txt',
1971 '/tmp/ test.txt',
1972 '/tmp/ tes\\t.txt',
1973 'test'
1974 ]
1975 for test in tests:
1976 print (test, fname_sanitize(test))
1977
1978 #-----------------------------------------------------------------------
1979 #test_coalesce()
1980 #test_capitalize()
1981 #test_import_module()
1982 test_mkdir()
1983 #test_gmPaths()
1984 #test_none_if()
1985 #test_bool2str()
1986 #test_bool2subst()
1987 #test_get_unique_filename()
1988 #test_size2str()
1989 #test_wrap()
1990 #test_input2decimal()
1991 #test_input2int()
1992 #test_unwrap()
1993 #test_md5()
1994 #test_unicode()
1995 #test_xml_escape()
1996 #test_gpg_decrypt()
1997 #test_strip_trailing_empty_lines()
1998 #test_fname_stem()
1999 #test_tex_escape()
2000 #test_dir_is_empty()
2001 #test_compare_dicts()
2002 #test_rm_dir()
2003 #test_rm_dir_content()
2004 #test_strip_prefix()
2005 #test_shorten_text()
2006 #test_format_compare_dicts()
2007 #test_fname_sanitize()
2008
2009 #===========================================================================
2010
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Thu May 10 01:55:20 2018 | http://epydoc.sourceforge.net |