1
2
3
4 __doc__ = """GNUmed general tools."""
5
6
7 __author__ = "K. Hilbert <Karsten.Hilbert@gmx.net>"
8 __license__ = "GPL v2 or later (details at http://www.gnu.org)"
9
10
11 import sys
12 import os
13 import os.path
14 import csv
15 import tempfile
16 import logging
17 import hashlib
18 import platform
19 import subprocess
20 import decimal
21 import getpass
22 import io
23 import functools
24 import json
25 import shutil
26 import zipfile
27 import datetime as pydt
28 import re as regex
29 import xml.sax.saxutils as xml_tools
30
31 import pickle, zlib
32
33 du_core = None
34
35
36
37 if __name__ == '__main__':
38 sys.path.insert(0, '../../')
39 from Gnumed.pycommon import gmBorg
40
41
42 _log = logging.getLogger('gm.tools')
43
44
45 ( CAPS_NONE,
46 CAPS_FIRST,
47 CAPS_ALLCAPS,
48 CAPS_WORDS,
49 CAPS_NAMES,
50 CAPS_FIRST_ONLY
51 ) = range(6)
52
53
54 u_currency_pound = '\u00A3'
55 u_currency_sign = '\u00A4'
56 u_currency_yen = '\u00A5'
57 u_right_double_angle_quote = '\u00AB'
58 u_registered_trademark = '\u00AE'
59 u_plus_minus = '\u00B1'
60 u_superscript_one = '\u00B9'
61 u_left_double_angle_quote = '\u00BB'
62 u_one_quarter = '\u00BC'
63 u_one_half = '\u00BD'
64 u_three_quarters = '\u00BE'
65 u_multiply = '\u00D7'
66 u_greek_ALPHA = '\u0391'
67 u_greek_alpha = '\u03b1'
68 u_greek_OMEGA = '\u03A9'
69 u_greek_omega = '\u03c9'
70 u_dagger = '\u2020'
71 u_triangular_bullet = '\u2023'
72 u_ellipsis = '\u2026'
73 u_euro = '\u20AC'
74 u_numero = '\u2116'
75 u_down_left_arrow = '\u21B5'
76 u_left_arrow = '\u2190'
77 u_up_arrow = '\u2191'
78 u_arrow2right = '\u2192'
79 u_down_arrow = '\u2193'
80 u_left_arrow_with_tail = '\u21a2'
81 u_arrow2right_from_bar = '\u21a6'
82 u_arrow2right_until_vertical_bar = '\u21e5'
83 u_sum = '\u2211'
84 u_almost_equal_to = '\u2248'
85 u_corresponds_to = '\u2258'
86 u_infinity = '\u221E'
87 u_arrow2right_until_vertical_bar2 = '\u2b72'
88 u_diameter = '\u2300'
89 u_checkmark_crossed_out = '\u237B'
90 u_box_horiz_high = '\u23ba'
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'
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
153
154 -def mkdir(directory=None, mode=None):
155 """Create directory.
156
157 - creates parent dirs if necessary
158 - does not fail if directory exists
159 <mode>: numeric, say 0o0700 for "-rwx------"
160 """
161 if os.path.isdir(directory):
162 if mode is None:
163 return True
164 try:
165 old_umask = os.umask(0)
166
167
168 os.chmod(directory, mode)
169 return True
170 except Exception:
171 _log.exception('cannot os.chmod(%s, %s)', oct(mode), directory)
172 raise
173 finally:
174 os.umask(old_umask)
175 return False
176
177 if mode is None:
178 os.makedirs(directory)
179 return True
180
181 try:
182 old_umask = os.umask(0)
183 os.makedirs(directory, mode)
184 return True
185 except Exception:
186 _log.exception('cannot os.makedirs(%s, %s)', oct(mode), directory)
187 raise
188 finally:
189 os.umask(old_umask)
190 return True
191
192
194
195 def _on_rm_error(func, path, exc):
196 _log.error('error while shutil.rmtree(%s)', path, exc_info=exc)
197 return True
198
199 error_count = 0
200 try:
201 shutil.rmtree(directory, False, _on_rm_error)
202 except Exception:
203 _log.exception('cannot shutil.rmtree(%s)', directory)
204 error_count += 1
205 return error_count
206
207
208 -def rm_dir_content(directory):
209 _log.debug('cleaning out [%s]', directory)
210 try:
211 items = os.listdir(directory)
212 except OSError:
213 return False
214 for item in items:
215
216 full_item = os.path.join(directory, item)
217 try:
218 os.remove(full_item)
219 except OSError:
220 _log.debug('[%s] seems to be a subdirectory', full_item)
221 errors = rmdir(full_item)
222 if errors > 0:
223 return False
224 except Exception:
225 _log.exception('cannot os.remove(%s) [a file or a link]', full_item)
226 return False
227
228 return True
229
230
232 if base_dir is None:
233 base_dir = gmPaths().tmp_dir
234 else:
235 if not os.path.isdir(base_dir):
236 mkdir(base_dir, mode = 0o0700)
237 if prefix is None:
238 prefix = 'sndbx-'
239 return tempfile.mkdtemp(prefix = prefix, suffix = '', dir = base_dir)
240
241
243 return os.path.abspath(os.path.join(directory, '..'))
244
245
247
248
249 return os.path.basename(os.path.normpath(directory))
250
251
253 try:
254 return len(os.listdir(directory)) == 0
255 except OSError as exc:
256 if exc.errno == 2:
257 return None
258 raise
259
260
261 -def copy_tree_content(directory, target_directory):
262 """Copy the *content* of <directory> *into* <target_directory>
263 which is created if need be.
264 """
265 assert (directory is not None), 'source <directory> should not be None'
266 _log.debug('copying content of [%s] into [%s]', directory, target_directory)
267 try:
268 items = os.listdir(directory)
269 except OSError:
270 return None
271
272 for item in items:
273 full_item = os.path.join(directory, item)
274 if os.path.isdir(full_item):
275 target_subdir = os.path.join(target_directory, item)
276 try:
277 shutil.copytree(full_item, target_subdir)
278 except Exception:
279 _log.exception('cannot copy subdir [%s]', full_item)
280 return None
281 else:
282 try:
283 shutil.copy2(full_item, target_directory)
284 except Exception:
285 _log.exception('cannot copy file [%s]', full_item)
286 return None
287
288 return target_directory
289
290
291
293 """This class provides the following paths:
294
295 .home_dir user home
296 .local_base_dir script installation dir
297 .working_dir current dir
298 .user_config_dir
299 .system_config_dir
300 .system_app_data_dir (not writable)
301 .tmp_dir instance-local
302 .user_tmp_dir user-local (NOT per instance)
303 .bytea_cache_dir caches downloaded BYTEA data
304 """
305 - def __init__(self, app_name=None, wx=None):
306 """Setup pathes.
307
308 <app_name> will default to (name of the script - .py)
309 """
310 try:
311 self.already_inited
312 return
313 except AttributeError:
314 pass
315
316 self.init_paths(app_name=app_name, wx=wx)
317 self.already_inited = True
318
319
320
321
323
324 if wx is None:
325 _log.debug('wxPython not available')
326 _log.debug('detecting paths directly')
327
328 if app_name is None:
329 app_name, ext = os.path.splitext(os.path.basename(sys.argv[0]))
330 _log.info('app name detected as [%s]', app_name)
331 else:
332 _log.info('app name passed in as [%s]', app_name)
333
334
335 self.__home_dir = None
336
337
338 if getattr(sys, 'frozen', False):
339 _log.info('frozen app, installed into temporary path')
340
341
342
343
344
345
346
347
348
349 self.local_base_dir = os.path.dirname(sys.executable)
350 else:
351 self.local_base_dir = os.path.abspath(os.path.dirname(sys.argv[0]))
352
353
354 self.working_dir = os.path.abspath(os.curdir)
355
356
357 mkdir(os.path.join(self.home_dir, '.%s' % app_name))
358 self.user_config_dir = os.path.join(self.home_dir, '.%s' % app_name)
359
360
361 try:
362 self.system_config_dir = os.path.join('/etc', app_name)
363 except ValueError:
364
365 self.system_config_dir = self.user_config_dir
366
367
368 try:
369 self.system_app_data_dir = os.path.join(sys.prefix, 'share', app_name)
370 except ValueError:
371 self.system_app_data_dir = self.local_base_dir
372
373
374 try:
375 self.__tmp_dir_already_set
376 _log.debug('temp dir already set')
377 except AttributeError:
378 _log.info('temp file prefix: %s', tempfile.gettempprefix())
379 _log.info('initial (user level) temp dir: %s', tempfile.gettempdir())
380 bytes_free = shutil.disk_usage(tempfile.gettempdir()).free
381 _log.info('free disk space for temp dir: %s (%s bytes)', size2str(size = bytes_free), bytes_free)
382
383 self.user_tmp_dir = os.path.join(tempfile.gettempdir(), app_name + '-' + getpass.getuser())
384 mkdir(self.user_tmp_dir, 0o700)
385 tempfile.tempdir = self.user_tmp_dir
386 _log.info('intermediate (app level) temp dir: %s', tempfile.gettempdir())
387
388 self.tmp_dir = tempfile.mkdtemp(prefix = 'g-')
389 _log.info('final (app instance level) temp dir: %s', tempfile.gettempdir())
390
391
392 cache_dir = os.path.join(self.user_tmp_dir, '.bytea_cache')
393 try:
394 stat = os.stat(cache_dir)
395 _log.warning('reusing BYTEA cache dir: %s', cache_dir)
396 _log.debug(stat)
397 except FileNotFoundError:
398 mkdir(cache_dir, mode = 0o0700)
399 self.bytea_cache_dir = cache_dir
400
401 self.__log_paths()
402 if wx is None:
403 return True
404
405
406 _log.debug('re-detecting paths with wxPython')
407
408 std_paths = wx.StandardPaths.Get()
409 _log.info('wxPython app name is [%s]', wx.GetApp().GetAppName())
410
411
412 mkdir(os.path.join(std_paths.GetUserConfigDir(), '.%s' % app_name))
413 self.user_config_dir = os.path.join(std_paths.GetUserConfigDir(), '.%s' % app_name)
414
415
416 try:
417 tmp = std_paths.GetConfigDir()
418 if not tmp.endswith(app_name):
419 tmp = os.path.join(tmp, app_name)
420 self.system_config_dir = tmp
421 except ValueError:
422
423 pass
424
425
426
427
428 if 'wxMSW' in wx.PlatformInfo:
429 _log.warning('this platform (wxMSW) sometimes returns a broken value for the system-wide application data dir')
430 else:
431 try:
432 self.system_app_data_dir = std_paths.GetDataDir()
433 except ValueError:
434 pass
435
436 self.__log_paths()
437 return True
438
439
441 _log.debug('sys.argv[0]: %s', sys.argv[0])
442 _log.debug('sys.executable: %s', sys.executable)
443 _log.debug('sys._MEIPASS: %s', getattr(sys, '_MEIPASS', '<not found>'))
444 _log.debug('os.environ["_MEIPASS2"]: %s', os.environ.get('_MEIPASS2', '<not found>'))
445 _log.debug('__file__ : %s', __file__)
446 _log.debug('local application base dir: %s', self.local_base_dir)
447 _log.debug('current working dir: %s', self.working_dir)
448 _log.debug('user home dir: %s', self.home_dir)
449 _log.debug('user-specific config dir: %s', self.user_config_dir)
450 _log.debug('system-wide config dir: %s', self.system_config_dir)
451 _log.debug('system-wide application data dir: %s', self.system_app_data_dir)
452 _log.debug('temporary dir (user): %s', self.user_tmp_dir)
453 _log.debug('temporary dir (instance): %s', self.tmp_dir)
454 _log.debug('BYTEA cache dir: %s', self.bytea_cache_dir)
455
456
457
458
460 if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)):
461 msg = '[%s:user_config_dir]: invalid path [%s]' % (self.__class__.__name__, path)
462 _log.error(msg)
463 raise ValueError(msg)
464 self.__user_config_dir = path
465
467 return self.__user_config_dir
468
469 user_config_dir = property(_get_user_config_dir, _set_user_config_dir)
470
472 if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)):
473 msg = '[%s:system_config_dir]: invalid path [%s]' % (self.__class__.__name__, path)
474 _log.error(msg)
475 raise ValueError(msg)
476 self.__system_config_dir = path
477
479 return self.__system_config_dir
480
481 system_config_dir = property(_get_system_config_dir, _set_system_config_dir)
482
484 if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)):
485 msg = '[%s:system_app_data_dir]: invalid path [%s]' % (self.__class__.__name__, path)
486 _log.error(msg)
487 raise ValueError(msg)
488 self.__system_app_data_dir = path
489
491 return self.__system_app_data_dir
492
493 system_app_data_dir = property(_get_system_app_data_dir, _set_system_app_data_dir)
494
496 raise ValueError('invalid to set home dir')
497
499 if self.__home_dir is not None:
500 return self.__home_dir
501
502 tmp = os.path.expanduser('~')
503 if tmp == '~':
504 _log.error('this platform does not expand ~ properly')
505 try:
506 tmp = os.environ['USERPROFILE']
507 except KeyError:
508 _log.error('cannot access $USERPROFILE in environment')
509
510 if not (
511 os.access(tmp, os.R_OK)
512 and
513 os.access(tmp, os.X_OK)
514 and
515 os.access(tmp, os.W_OK)
516 ):
517 msg = '[%s:home_dir]: invalid path [%s]' % (self.__class__.__name__, tmp)
518 _log.error(msg)
519 raise ValueError(msg)
520
521 self.__home_dir = tmp
522 return self.__home_dir
523
524 home_dir = property(_get_home_dir, _set_home_dir)
525
526
528 if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)):
529 msg = '[%s:tmp_dir]: invalid path [%s]' % (self.__class__.__name__, path)
530 _log.error(msg)
531 raise ValueError(msg)
532 _log.debug('previous temp dir: %s', tempfile.gettempdir())
533 self.__tmp_dir = path
534 tempfile.tempdir = self.__tmp_dir
535 _log.debug('new temp dir: %s', tempfile.gettempdir())
536 self.__tmp_dir_already_set = True
537
539 return self.__tmp_dir
540
541 tmp_dir = property(_get_tmp_dir, _set_tmp_dir)
542
543
544
545
546 -def recode_file(source_file=None, target_file=None, source_encoding='utf8', target_encoding=None, base_dir=None, error_mode='replace'):
547 if target_encoding is None:
548 return source_file
549 if target_encoding == source_encoding:
550 return source_file
551 if target_file is None:
552 target_file = get_unique_filename (
553 prefix = '%s-%s_%s-' % (fname_stem(source_file), source_encoding, target_encoding),
554 suffix = fname_extension(source_file, '.txt'),
555 tmp_dir = base_dir
556 )
557
558 _log.debug('[%s] -> [%s] (%s -> %s)', source_encoding, target_encoding, source_file, target_file)
559
560 in_file = io.open(source_file, mode = 'rt', encoding = source_encoding)
561 out_file = io.open(target_file, mode = 'wt', encoding = target_encoding, errors = error_mode)
562 for line in in_file:
563 out_file.write(line)
564 out_file.close()
565 in_file.close()
566
567 return target_file
568
569
570 -def unzip_archive(archive_name, target_dir=None, remove_archive=False):
571 _log.debug('unzipping [%s] -> [%s]', archive_name, target_dir)
572 success = False
573 try:
574 with zipfile.ZipFile(archive_name) as archive:
575 archive.extractall(target_dir)
576 success = True
577 except Exception:
578 _log.exception('cannot unzip')
579 return False
580 if remove_archive:
581 remove_file(archive_name)
582 return success
583
584
585 -def remove_file(filename, log_error=True, force=False):
610
611
612 -def file2md5(filename=None, return_hex=True):
613 blocksize = 2**10 * 128
614 _log.debug('md5(%s): <%s> byte blocks', filename, blocksize)
615
616 f = io.open(filename, mode = 'rb')
617
618 md5 = hashlib.md5()
619 while True:
620 data = f.read(blocksize)
621 if not data:
622 break
623 md5.update(data)
624 f.close()
625
626 _log.debug('md5(%s): %s', filename, md5.hexdigest())
627
628 if return_hex:
629 return md5.hexdigest()
630 return md5.digest()
631
632
634 _log.debug('chunked_md5(%s, chunk_size=%s bytes)', filename, chunk_size)
635 md5_concat = ''
636 f = open(filename, 'rb')
637 while True:
638 md5 = hashlib.md5()
639 data = f.read(chunk_size)
640 if not data:
641 break
642 md5.update(data)
643 md5_concat += md5.hexdigest()
644 f.close()
645 md5 = hashlib.md5()
646 md5.update(md5_concat)
647 hex_digest = md5.hexdigest()
648 _log.debug('md5("%s"): %s', md5_concat, hex_digest)
649 return hex_digest
650
651
652 default_csv_reader_rest_key = 'list_of_values_of_unknown_fields'
653
655 try:
656 is_dict_reader = kwargs['dict']
657 del kwargs['dict']
658 except KeyError:
659 is_dict_reader = False
660
661 if is_dict_reader:
662 kwargs['restkey'] = default_csv_reader_rest_key
663 return csv.DictReader(unicode_csv_data, dialect=dialect, **kwargs)
664 return csv.reader(csv_data, dialect=dialect, **kwargs)
665
666
667
668
670 for line in unicode_csv_data:
671 yield line.encode(encoding)
672
673
674
675
676
678
679
680 try:
681 is_dict_reader = kwargs['dict']
682 del kwargs['dict']
683 if is_dict_reader is not True:
684 raise KeyError
685 kwargs['restkey'] = default_csv_reader_rest_key
686 csv_reader = csv.DictReader(unicode2charset_encoder(unicode_csv_data), dialect=dialect, **kwargs)
687 except KeyError:
688 is_dict_reader = False
689 csv_reader = csv.reader(unicode2charset_encoder(unicode_csv_data), dialect=dialect, **kwargs)
690
691 for row in csv_reader:
692
693 if is_dict_reader:
694 for key in row.keys():
695 if key == default_csv_reader_rest_key:
696 old_data = row[key]
697 new_data = []
698 for val in old_data:
699 new_data.append(str(val, encoding))
700 row[key] = new_data
701 if default_csv_reader_rest_key not in csv_reader.fieldnames:
702 csv_reader.fieldnames.append(default_csv_reader_rest_key)
703 else:
704 row[key] = str(row[key], encoding)
705 yield row
706 else:
707 yield [ str(cell, encoding) for cell in row ]
708
709
710
712 """Normalizes unicode, removes non-alpha characters, converts spaces to underscores."""
713
714 dir_part, name_part = os.path.split(filename)
715 if name_part == '':
716 return filename
717
718 import unicodedata
719 name_part = unicodedata.normalize('NFKD', name_part)
720
721 name_part = regex.sub (
722 '[^.\w\s[\]()%§+-]',
723 '',
724 name_part,
725 flags = regex.UNICODE
726 ).strip()
727
728 name_part = regex.sub (
729 '\s+',
730 '_',
731 name_part,
732 flags = regex.UNICODE
733 )
734 return os.path.join(dir_part, name_part)
735
736
738 """/home/user/dir/filename.ext -> filename"""
739 return os.path.splitext(os.path.basename(filename))[0]
740
741
743 """/home/user/dir/filename.ext -> /home/user/dir/filename"""
744 return os.path.splitext(filename)[0]
745
746
748 """ /home/user/dir/filename.ext -> .ext
749 '' or '.' -> fallback if any else ''
750 """
751 ext = os.path.splitext(filename)[1]
752 if ext.strip() not in ['.', '']:
753 return ext
754 if fallback is None:
755 return ''
756 return fallback
757
758
760
761 return os.path.split(filename)[0]
762
763
765
766 return os.path.split(filename)[1]
767
768
770 """This function has a race condition between
771 its file.close()
772 and actually
773 using the filename in callers.
774
775 The file will NOT exist after calling this function.
776 """
777 if tmp_dir is not None:
778 if (
779 not os.access(tmp_dir, os.F_OK)
780 or
781 not os.access(tmp_dir, os.X_OK | os.W_OK)
782 ):
783 _log.warning('cannot os.access() temporary dir [%s], using system default', tmp_dir)
784 tmp_dir = None
785
786 if include_timestamp:
787 ts = pydt.datetime.now().strftime('%m%d-%H%M%S-')
788 else:
789 ts = ''
790
791 kwargs = {
792 'dir': tmp_dir,
793
794
795 'delete': True
796 }
797
798 if prefix is None:
799 kwargs['prefix'] = 'gm-%s' % ts
800 else:
801 kwargs['prefix'] = prefix + ts
802
803 if suffix in [None, '']:
804 kwargs['suffix'] = '.tmp'
805 else:
806 if not suffix.startswith('.'):
807 suffix = '.' + suffix
808 kwargs['suffix'] = suffix
809
810 f = tempfile.NamedTemporaryFile(**kwargs)
811 filename = f.name
812 f.close()
813
814 return filename
815
816
818 import ctypes
819
820 kernel32 = ctype.WinDLL('kernel32', use_last_error = True)
821 windows_create_symlink = kernel32.CreateSymbolicLinkW
822 windows_create_symlink.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_uint32)
823 windows_create_symlink.restype = ctypes.c_ubyte
824 if os.path.isdir(physical_name):
825 flags = 1
826 else:
827 flags = 0
828 ret_code = windows_create_symlink(link_name, physical_name.replace('/', '\\'), flags)
829 _log.debug('ctypes.windll.kernel32.CreateSymbolicLinkW() [%s] exit code: %s', windows_create_symlink, ret_code)
830 if ret_code == 0:
831 raise ctypes.WinError()
832 return ret_code
833
834
835 -def mklink(physical_name, link_name, overwrite=False):
836
837 _log.debug('creating symlink (overwrite = %s):', overwrite)
838 _log.debug('link [%s] =>', link_name)
839 _log.debug('=> physical [%s]', physical_name)
840
841 if os.path.exists(link_name):
842 _log.debug('link exists')
843 if overwrite:
844 return True
845 return False
846
847 try:
848 os.symlink(physical_name, link_name)
849 except (AttributeError, NotImplementedError):
850 _log.debug('this Python does not have os.symlink(), trying via ctypes')
851 __make_symlink_on_windows(physical_name, link_name)
852 except PermissionError:
853 _log.exception('cannot create link')
854 return False
855
856
857 return True
858
859
861 """Import a module from any location."""
862
863 _log.debug('CWD: %s', os.getcwd())
864
865 remove_path = always_remove_path or False
866 if module_path not in sys.path:
867 _log.info('appending to sys.path: [%s]' % module_path)
868 sys.path.append(module_path)
869 remove_path = True
870
871 _log.debug('will remove import path: %s', remove_path)
872
873 if module_name.endswith('.py'):
874 module_name = module_name[:-3]
875
876 try:
877 module = __import__(module_name)
878 except Exception:
879 _log.exception('cannot __import__() module [%s] from [%s]' % (module_name, module_path))
880 while module_path in sys.path:
881 sys.path.remove(module_path)
882 raise
883
884 _log.info('imported module [%s] as [%s]' % (module_name, module))
885 if remove_path:
886 while module_path in sys.path:
887 sys.path.remove(module_path)
888
889 return module
890
891
892
893
895 if size == 1:
896 return template % _('1 Byte')
897 if size < 10 * _kB:
898 return template % _('%s Bytes') % size
899 if size < _MB:
900 return template % '%.1f kB' % (float(size) / _kB)
901 if size < _GB:
902 return template % '%.1f MB' % (float(size) / _MB)
903 if size < _TB:
904 return template % '%.1f GB' % (float(size) / _GB)
905 if size < _PB:
906 return template % '%.1f TB' % (float(size) / _TB)
907 return template % '%.1f PB' % (float(size) / _PB)
908
909
910 -def bool2subst(boolean=None, true_return=True, false_return=False, none_return=None):
911 if boolean is None:
912 return none_return
913 if boolean:
914 return true_return
915 if not boolean:
916 return false_return
917 raise ValueError('bool2subst(): <boolean> arg must be either of True, False, None')
918
919
920 -def bool2str(boolean=None, true_str='True', false_str='False'):
921 return bool2subst (
922 boolean = bool(boolean),
923 true_return = true_str,
924 false_return = false_str
925 )
926
927
928 -def none_if(value=None, none_equivalent=None, strip_string=False):
929 """Modelled after the SQL NULLIF function."""
930 if value is None:
931 return None
932 if strip_string:
933 stripped = value.strip()
934 else:
935 stripped = value
936 if stripped == none_equivalent:
937 return None
938 return value
939
940
941 -def coalesce(value2test=None, return_instead=None, template4value=None, template4instead=None, none_equivalents=None, function4value=None, value2return=None):
942 """Modelled after the SQL coalesce function.
943
944 To be used to simplify constructs like:
945
946 if value2test is None (or in none_equivalents):
947 value = (template4instead % return_instead) or return_instead
948 else:
949 value = (template4value % value2test) or value2test
950 print value
951
952 @param value2test: the value to be tested for <None>
953 @type value2test: any Python type, must have a __str__ method if template4value is not None
954 @param return_instead: the value to be returned if <initial> is None
955 @type return_instead: any Python type, must have a __str__ method if template4instead is not None
956 @param template4value: if <initial> is returned replace the value into this template, must contain one <%s>
957 @type template4value: string or None
958 @param template4instead: if <return_instead> is returned replace the value into this template, must contain one <%s>
959 @type template4instead: string or None
960
961 example:
962 function4value = ('strftime', '%Y-%m-%d')
963
964 Ideas:
965 - list of return_insteads: initial, [return_instead, template], [return_instead, template], [return_instead, template], template4value, ...
966 """
967 if none_equivalents is None:
968 none_equivalents = [None]
969
970 if value2test in none_equivalents:
971 if template4instead is None:
972 return return_instead
973 return template4instead % return_instead
974
975
976
977
978 if value2return is not None:
979 return value2return
980
981 value2return = value2test
982
983 if function4value is not None:
984 funcname, args = function4value
985 func = getattr(value2test, funcname)
986 value2return = func(args)
987
988
989 if template4value is None:
990 return value2return
991
992 try:
993 return template4value % value2return
994 except TypeError:
995
996
997
998
999 if hasattr(_log, 'log_stack_trace'):
1000 _log.log_stack_trace(message = 'deprecated use of <template4value> for <value2return>')
1001 else:
1002 _log.error('deprecated use of <template4value> for <value2return>')
1003 _log.error(locals())
1004 return template4value
1005
1006
1008 val = match_obj.group(0).lower()
1009 if val in ['von', 'van', 'de', 'la', 'l', 'der', 'den']:
1010 return val
1011 buf = list(val)
1012 buf[0] = buf[0].upper()
1013 for part in ['mac', 'mc', 'de', 'la']:
1014 if len(val) > len(part) and val[:len(part)] == part:
1015 buf[len(part)] = buf[len(part)].upper()
1016 return ''.join(buf)
1017
1018
1020 """Capitalize the first character but leave the rest alone.
1021
1022 Note that we must be careful about the locale, this may
1023 have issues ! However, for UTF strings it should just work.
1024 """
1025 if (mode is None) or (mode == CAPS_NONE):
1026 return text
1027
1028 if len(text) == 0:
1029 return text
1030
1031 if mode == CAPS_FIRST:
1032 if len(text) == 1:
1033 return text[0].upper()
1034 return text[0].upper() + text[1:]
1035
1036 if mode == CAPS_ALLCAPS:
1037 return text.upper()
1038
1039 if mode == CAPS_FIRST_ONLY:
1040
1041
1042 return text[0].upper() + text[1:].lower()
1043
1044 if mode == CAPS_WORDS:
1045
1046 return regex.sub(r'(\w)(\w+)', lambda x: x.group(1).upper() + x.group(2).lower(), text)
1047
1048 if mode == CAPS_NAMES:
1049
1050 return capitalize(text=text, mode=CAPS_FIRST)
1051
1052 print("ERROR: invalid capitalization mode: [%s], leaving input as is" % mode)
1053 return text
1054
1055
1077
1078
1104
1105
1106 -def strip_prefix(text, prefix, remove_repeats=False, remove_whitespace=False):
1107 if remove_repeats:
1108 if remove_whitespace:
1109 while text.lstrip().startswith(prefix):
1110 text = text.lstrip().replace(prefix, '', 1).lstrip()
1111 return text
1112 while text.startswith(prefix):
1113 text = text.replace(prefix, '', 1)
1114 return text
1115 if remove_whitespace:
1116 return text.lstrip().replace(prefix, '', 1).lstrip()
1117 return text.replace(prefix, '', 1)
1118
1119
1120 -def strip_suffix(text, suffix, remove_repeats=False, remove_whitespace=False):
1121 suffix_len = len(suffix)
1122 if remove_repeats:
1123 if remove_whitespace:
1124 while text.rstrip().endswith(suffix):
1125 text = text.rstrip()[:-suffix_len].rstrip()
1126 return text
1127 while text.endswith(suffix):
1128 text = text[:-suffix_len]
1129 return text
1130 if remove_whitespace:
1131 return text.rstrip()[:-suffix_len].rstrip()
1132 return text[:-suffix_len]
1133
1134
1136 if lines is None:
1137 lines = text.split(eol)
1138
1139 while True:
1140 if lines[0].strip(eol).strip() != '':
1141 break
1142 lines = lines[1:]
1143
1144 if return_list:
1145 return lines
1146
1147 return eol.join(lines)
1148
1149
1151 if lines is None:
1152 lines = text.split(eol)
1153
1154 while True:
1155 if lines[-1].strip(eol).strip() != '':
1156 break
1157 lines = lines[:-1]
1158
1159 if return_list:
1160 return lines
1161
1162 return eol.join(lines)
1163
1164
1172
1173
1174 -def list2text(lines, initial_indent='', subsequent_indent='', eol='\n', strip_leading_empty_lines=True, strip_trailing_empty_lines=True, strip_trailing_whitespace=True, max_line_width=None):
1175
1176 if len(lines) == 0:
1177 return ''
1178
1179 if strip_leading_empty_lines:
1180 lines = strip_leading_empty_lines(lines = lines, eol = eol, return_list = True)
1181
1182 if strip_trailing_empty_lines:
1183 lines = strip_trailing_empty_lines(lines = lines, eol = eol, return_list = True)
1184
1185 if strip_trailing_whitespace:
1186 lines = [ l.rstrip() for l in lines ]
1187
1188 if max_line_width is not None:
1189 wrapped_lines = []
1190 for l in lines:
1191 wrapped_lines.extend(wrap(l, max_line_width).split('\n'))
1192 lines = wrapped_lines
1193
1194 indented_lines = [initial_indent + lines[0]]
1195 indented_lines.extend([ subsequent_indent + l for l in lines[1:] ])
1196
1197 return eol.join(indented_lines)
1198
1199
1200 -def wrap(text=None, width=None, initial_indent='', subsequent_indent='', eol='\n'):
1201 """A word-wrap function that preserves existing line breaks
1202 and most spaces in the text. Expects that existing line
1203 breaks are posix newlines (\n).
1204 """
1205 if width is None:
1206 return text
1207
1208 wrapped = initial_indent + functools.reduce (
1209 lambda line, word, width=width: '%s%s%s' % (
1210 line,
1211 ' \n'[(len(line) - line.rfind('\n') - 1 + len(word.split('\n',1)[0]) >= width)],
1212 word
1213 ),
1214 text.split(' ')
1215 )
1216 if subsequent_indent != '':
1217 wrapped = ('\n%s' % subsequent_indent).join(wrapped.split('\n'))
1218 if eol != '\n':
1219 wrapped = wrapped.replace('\n', eol)
1220 return wrapped
1221
1222
1223 -def unwrap(text=None, max_length=None, strip_whitespace=True, remove_empty_lines=True, line_separator = ' // '):
1224
1225 text = text.replace('\r', '')
1226 lines = text.split('\n')
1227 text = ''
1228 for line in lines:
1229
1230 if strip_whitespace:
1231 line = line.strip().strip('\t').strip()
1232
1233 if remove_empty_lines:
1234 if line == '':
1235 continue
1236
1237 text += ('%s%s' % (line, line_separator))
1238
1239 text = text.rstrip(line_separator)
1240
1241 if max_length is not None:
1242 text = text[:max_length]
1243
1244 text = text.rstrip(line_separator)
1245
1246 return text
1247
1248
1249 -def shorten_text(text=None, max_length=None):
1250
1251 if len(text) <= max_length:
1252 return text
1253
1254 return text[:max_length-1] + u_ellipsis
1255
1256
1258 if text is None:
1259 return None
1260 if max_length is None:
1261 max_length = len(text)
1262 else:
1263 if len(text) <= max_length:
1264 return text
1265 old_words = regex.split('\s+', text, flags = regex.UNICODE)
1266 no_old_words = len(old_words)
1267 max_word_length = max(min_word_length, (max_length // no_old_words))
1268 words = []
1269 for word in old_words:
1270 if len(word) <= max_word_length:
1271 words.append(word)
1272 continue
1273 if ignore_numbers:
1274 tmp = word.replace('-', '').replace('+', '').replace('.', '').replace(',', '').replace('/', '').replace('&', '').replace('*', '')
1275 if tmp.isdigit():
1276 words.append(word)
1277 continue
1278 words.append(word[:max_word_length] + ellipsis)
1279 return ' '.join(words)
1280
1281
1283 """check for special XML characters and transform them"""
1284 return xml_tools.escape(text)
1285
1286
1287 -def tex_escape_string(text=None, replace_known_unicode=True, replace_eol=False, keep_visual_eol=False):
1288 """Check for special TeX characters and transform them.
1289
1290 replace_eol:
1291 replaces "\n" with "\\newline"
1292 keep_visual_eol:
1293 replaces "\n" with "\\newline \n" such that
1294 both LaTeX will know to place a line break
1295 at this point as well as the visual formatting
1296 is preserved in the LaTeX source (think multi-
1297 row table cells)
1298 """
1299 text = text.replace('\\', '\\textbackslash')
1300 text = text.replace('^', '\\textasciicircum')
1301 text = text.replace('~', '\\textasciitilde')
1302
1303 text = text.replace('{', '\\{')
1304 text = text.replace('}', '\\}')
1305 text = text.replace('%', '\\%')
1306 text = text.replace('&', '\\&')
1307 text = text.replace('#', '\\#')
1308 text = text.replace('$', '\\$')
1309 text = text.replace('_', '\\_')
1310 if replace_eol:
1311 if keep_visual_eol:
1312 text = text.replace('\n', '\\newline \n')
1313 else:
1314 text = text.replace('\n', '\\newline ')
1315
1316 if replace_known_unicode:
1317
1318 text = text.replace(u_euro, '\\EUR')
1319 text = text.replace(u_sum, '$\\Sigma$')
1320
1321 return text
1322
1323
1325 global du_core
1326 if du_core is None:
1327 try:
1328 from docutils import core as du_core
1329 except ImportError:
1330 _log.warning('cannot turn ReST into LaTeX: docutils not installed')
1331 return tex_escape_string(text = rst_text)
1332
1333 parts = du_core.publish_parts (
1334 source = rst_text.replace('\\', '\\\\'),
1335 source_path = '<internal>',
1336 writer_name = 'latex',
1337
1338 settings_overrides = {
1339 'input_encoding': 'unicode'
1340 },
1341 enable_exit_status = True
1342 )
1343 return parts['body']
1344
1345
1346 -def rst2html(rst_text, replace_eol=False, keep_visual_eol=False):
1347 global du_core
1348 if du_core is None:
1349 try:
1350 from docutils import core as du_core
1351 except ImportError:
1352 _log.warning('cannot turn ReST into HTML: docutils not installed')
1353 return html_escape_string(text = rst_text, replace_eol=False, keep_visual_eol=False)
1354
1355 parts = du_core.publish_parts (
1356 source = rst_text.replace('\\', '\\\\'),
1357 source_path = '<internal>',
1358 writer_name = 'latex',
1359
1360 settings_overrides = {
1361 'input_encoding': 'unicode'
1362 },
1363 enable_exit_status = True
1364 )
1365 return parts['body']
1366
1367
1372
1373
1374 __html_escape_table = {
1375 "&": "&",
1376 '"': """,
1377 "'": "'",
1378 ">": ">",
1379 "<": "<",
1380 }
1381
1390
1391
1394
1395
1397 if isinstance(obj, pydt.datetime):
1398 return obj.isoformat()
1399 raise TypeError('cannot json_serialize(%s)' % type(obj))
1400
1401
1402
1404 _log.info('comparing dict-likes: %s[%s] vs %s[%s]', coalesce(title1, '', '"%s" '), type(d1), coalesce(title2, '', '"%s" '), type(d2))
1405 try:
1406 d1 = dict(d1)
1407 except TypeError:
1408 pass
1409 try:
1410 d2 = dict(d2)
1411 except TypeError:
1412 pass
1413 keys_d1 = frozenset(d1.keys())
1414 keys_d2 = frozenset(d2.keys())
1415 different = False
1416 if len(keys_d1) != len(keys_d2):
1417 _log.info('different number of keys: %s vs %s', len(keys_d1), len(keys_d2))
1418 different = True
1419 for key in keys_d1:
1420 if key in keys_d2:
1421 if type(d1[key]) != type(d2[key]):
1422 _log.info('%25.25s: type(dict1) = %s = >>>%s<<<' % (key, type(d1[key]), d1[key]))
1423 _log.info('%25.25s type(dict2) = %s = >>>%s<<<' % ('', type(d2[key]), d2[key]))
1424 different = True
1425 continue
1426 if d1[key] == d2[key]:
1427 _log.info('%25.25s: both = >>>%s<<<' % (key, d1[key]))
1428 else:
1429 _log.info('%25.25s: dict1 = >>>%s<<<' % (key, d1[key]))
1430 _log.info('%25.25s dict2 = >>>%s<<<' % ('', d2[key]))
1431 different = True
1432 else:
1433 _log.info('%25.25s: %50.50s | <MISSING>' % (key, '>>>%s<<<' % d1[key]))
1434 different = True
1435 for key in keys_d2:
1436 if key in keys_d1:
1437 continue
1438 _log.info('%25.25s: %50.50s | %.50s' % (key, '<MISSING>', '>>>%s<<<' % d2[key]))
1439 different = True
1440 if different:
1441 _log.info('dict-likes appear to be different from each other')
1442 return False
1443 _log.info('dict-likes appear equal to each other')
1444 return True
1445
1446
1539
1540
1586
1587
1588 -def dicts2table(dict_list, left_margin=0, eol='\n', keys2ignore=None, headers=None, show_only_changes=False, equality_value='<=>', date_format=None):
1589
1590 keys2show = []
1591 dict_max_size = {}
1592 max_row_label_size = 0
1593 if keys2ignore is None:
1594 keys2ignore = []
1595
1596
1597 for d in dict_list:
1598 dict_max_size[id(d)] = 0
1599 for key in d.keys():
1600 if key in keys2ignore:
1601 continue
1602 dict_max_size[id(d)] = max(dict_max_size[id(d)], len('%s' % d[key]))
1603 if key in keys2show:
1604 continue
1605 keys2show.append(key)
1606 max_row_label_size = max(max_row_label_size, len('%s' % key))
1607
1608
1609 lines = { k: [] for k in keys2show }
1610 prev_vals = {}
1611 for d in dict_list:
1612 if show_only_changes:
1613 max_size = max(dict_max_size[id(d)], len(equality_value))
1614 else:
1615 max_size = dict_max_size[id(d)]
1616 max_len_str = '%s.%s' % (max_size, max_size)
1617 field_template = ' %' + max_len_str + 's'
1618 for key in keys2show:
1619 try:
1620 val = d[key]
1621 except KeyError:
1622 lines[key].append(field_template % _('<missing>'))
1623 continue
1624 if isinstance(val, pydt.datetime):
1625 if date_format is not None:
1626 val = val.strftime(date_format)
1627 lines[key].append(field_template % val)
1628 if show_only_changes:
1629 if key not in prev_vals:
1630 prev_vals[key] = '%s' % lines[key][-1]
1631 continue
1632 if lines[key][-1] != prev_vals[key]:
1633 prev_vals[key] = '%s' % lines[key][-1]
1634 continue
1635 lines[key][-1] = field_template % equality_value
1636
1637
1638 table_lines = []
1639 max_len_str = '%s.%s' % (max_row_label_size, max_row_label_size)
1640 row_label_template = '%' + max_len_str + 's'
1641 for key in lines:
1642 line = (' ' * left_margin) + row_label_template % key + '|'
1643 line += '|'.join(lines[key])
1644 table_lines.append(line)
1645
1646
1647 if headers is not None:
1648 header_line1 = (' ' * left_margin) + row_label_template % ''
1649 header_line2 = (' ' * left_margin) + u_box_horiz_single * (max_row_label_size)
1650 header_sizes = [ max(dict_max_size[dict_id], len(equality_value)) for dict_id in dict_max_size ]
1651 for idx in range(len(headers)):
1652 max_len_str = '%s.%s' % (header_sizes[idx], header_sizes[idx])
1653 header_template = '%' + max_len_str + 's'
1654 header_line1 += '| '
1655 header_line1 += header_template % headers[idx]
1656 header_line2 += '%s%s' % (u_box_plus, u_box_horiz_single)
1657 header_line2 += u_box_horiz_single * header_sizes[idx]
1658 table_lines.insert(0, header_line2)
1659 table_lines.insert(0, header_line1)
1660
1661 if eol is None:
1662 return table_lines
1663
1664 return ('|' + eol).join(table_lines) + '|' + eol
1665
1666
1668 for key in required_keys:
1669 try:
1670 d[key]
1671 except KeyError:
1672 if missing_key_template is None:
1673 d[key] = None
1674 else:
1675 d[key] = missing_key_template % {'key': key}
1676 return d
1677
1678
1680 try:
1681 import pyudev
1682 import psutil
1683 except ImportError:
1684 _log.error('pyudev and/or psutil not installed')
1685 return {}
1686
1687 removable_partitions = {}
1688 ctxt = pyudev.Context()
1689 removable_devices = [ dev for dev in ctxt.list_devices(subsystem='block', DEVTYPE='disk') if dev.attributes.get('removable') == b'1' ]
1690 all_mounted_partitions = { part.device: part for part in psutil.disk_partitions() }
1691 for device in removable_devices:
1692 _log.debug('removable device: %s', device.properties['ID_MODEL'])
1693 partitions_on_removable_device = {
1694 part.device_node: {
1695 'type': device.properties['ID_TYPE'],
1696 'bus': device.properties['ID_BUS'],
1697 'device': device.properties['DEVNAME'],
1698 'partition': part.properties['DEVNAME'],
1699 'vendor': part.properties['ID_VENDOR'],
1700 'model': part.properties['ID_MODEL'],
1701 'fs_label': part.properties['ID_FS_LABEL'],
1702 'is_mounted': False,
1703 'mountpoint': None,
1704 'fs_type': None,
1705 'size_in_bytes': -1,
1706 'bytes_free': 0
1707 } for part in ctxt.list_devices(subsystem='block', DEVTYPE='partition', parent=device)
1708 }
1709 for part in partitions_on_removable_device:
1710 try:
1711 partitions_on_removable_device[part]['mountpoint'] = all_mounted_partitions[part].mountpoint
1712 partitions_on_removable_device[part]['is_mounted'] = True
1713 partitions_on_removable_device[part]['fs_type'] = all_mounted_partitions[part].fstype
1714 du = shutil.disk_usage(all_mounted_partitions[part].mountpoint)
1715 partitions_on_removable_device[part]['size_in_bytes'] = du.total
1716 partitions_on_removable_device[part]['bytes_free'] = du.free
1717 except KeyError:
1718 pass
1719 removable_partitions.update(partitions_on_removable_device)
1720 return removable_partitions
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1733 try:
1734 import pyudev
1735 except ImportError:
1736 _log.error('pyudev not installed')
1737 return []
1738
1739 optical_writers = []
1740 ctxt = pyudev.Context()
1741 for dev in [ dev for dev in ctxt.list_devices(subsystem='block', DEVTYPE='disk') if dev.properties.get('ID_CDROM_CD_RW', None) == '1' ]:
1742 optical_writers.append ({
1743 'type': dev.properties['ID_TYPE'],
1744 'bus': dev.properties['ID_BUS'],
1745 'device': dev.properties['DEVNAME'],
1746 'model': dev.properties['ID_MODEL']
1747 })
1748 return optical_writers
1749
1750
1751
1778
1779
1780
1781
1782
1783 __icon_serpent = \
1784 """x\xdae\x8f\xb1\x0e\x83 \x10\x86w\x9f\xe2\x92\x1blb\xf2\x07\x96\xeaH:0\xd6\
1785 \xc1\x85\xd5\x98N5\xa5\xef?\xf5N\xd0\x8a\xdcA\xc2\xf7qw\x84\xdb\xfa\xb5\xcd\
1786 \xd4\xda;\xc9\x1a\xc8\xb6\xcd<\xb5\xa0\x85\x1e\xeb\xbc\xbc7b!\xf6\xdeHl\x1c\
1787 \x94\x073\xec<*\xf7\xbe\xf7\x99\x9d\xb21~\xe7.\xf5\x1f\x1c\xd3\xbdVlL\xc2\
1788 \xcf\xf8ye\xd0\x00\x90\x0etH \x84\x80B\xaa\x8a\x88\x85\xc4(U\x9d$\xfeR;\xc5J\
1789 \xa6\x01\xbbt9\xceR\xc8\x81e_$\x98\xb9\x9c\xa9\x8d,y\xa9t\xc8\xcf\x152\xe0x\
1790 \xe9$\xf5\x07\x95\x0cD\x95t:\xb1\x92\xae\x9cI\xa8~\x84\x1f\xe0\xa3ec"""
1791
1793
1794 paths = gmPaths(app_name = 'gnumed', wx = wx)
1795
1796 candidates = [
1797 os.path.join(paths.system_app_data_dir, 'bitmaps', 'gm_icon-serpent_and_gnu.png'),
1798 os.path.join(paths.local_base_dir, 'bitmaps', 'gm_icon-serpent_and_gnu.png'),
1799 os.path.join(paths.system_app_data_dir, 'bitmaps', 'serpent.png'),
1800 os.path.join(paths.local_base_dir, 'bitmaps', 'serpent.png')
1801 ]
1802
1803 found_as = None
1804 for candidate in candidates:
1805 try:
1806 open(candidate, 'r').close()
1807 found_as = candidate
1808 break
1809 except IOError:
1810 _log.debug('icon not found in [%s]', candidate)
1811
1812 if found_as is None:
1813 _log.warning('no icon file found, falling back to builtin (ugly) icon')
1814 icon_bmp_data = wx.BitmapFromXPMData(pickle.loads(zlib.decompress(__icon_serpent)))
1815 icon.CopyFromBitmap(icon_bmp_data)
1816 else:
1817 _log.debug('icon found in [%s]', found_as)
1818 icon = wx.Icon()
1819 try:
1820 icon.LoadFile(found_as, wx.BITMAP_TYPE_ANY)
1821 except AttributeError:
1822 _log.exception("this platform doesn't support wx.Icon().LoadFile()")
1823
1824 return icon
1825
1826
1827 -def create_qrcode(text=None, filename=None, qr_filename=None, verbose=False):
1828 assert (not ((text is None) and (filename is None))), 'either <text> or <filename> must be specified'
1829
1830 try:
1831 import pyqrcode
1832 except ImportError:
1833 _log.exception('cannot import <pyqrcode>')
1834 return None
1835 if text is None:
1836 with io.open(filename, mode = 'rt', encoding = 'utf8') as input_file:
1837 text = input_file.read()
1838 if qr_filename is None:
1839 if filename is None:
1840 qr_filename = get_unique_filename(prefix = 'gm-qr-', suffix = '.png')
1841 else:
1842 qr_filename = get_unique_filename (
1843 prefix = fname_stem(filename) + '-',
1844 suffix = fname_extension(filename) + '.png'
1845 )
1846 _log.debug('[%s] -> [%s]', filename, qr_filename)
1847 qr = pyqrcode.create(text, encoding = 'utf8')
1848 if verbose:
1849 print('input file:', filename)
1850 print('output file:', qr_filename)
1851 print('text to encode:', text)
1852 print(qr.terminal())
1853 qr.png(qr_filename, quiet_zone = 1)
1854 return qr_filename
1855
1856
1857
1858
1859 if __name__ == '__main__':
1860
1861 if len(sys.argv) < 2:
1862 sys.exit()
1863
1864 if sys.argv[1] != 'test':
1865 sys.exit()
1866
1867
1868 logging.basicConfig(level = logging.DEBUG)
1869 from Gnumed.pycommon import gmI18N
1870 gmI18N.activate_locale()
1871 gmI18N.install_domain()
1872
1873
1931
1936
1938
1939 val = None
1940 print(val, coalesce(val, 'is None', 'is not None'))
1941 val = 1
1942 print(val, coalesce(val, 'is None', 'is not None'))
1943 return
1944
1945 import datetime as dt
1946 print(coalesce(value2test = dt.datetime.now(), template4value = '-- %s --', function4value = ('strftime', '%Y-%m-%d')))
1947
1948 print('testing coalesce()')
1949 print("------------------")
1950 tests = [
1951 [None, 'something other than <None>', None, None, 'something other than <None>'],
1952 ['Captain', 'Mr.', '%s.'[:4], 'Mr.', 'Capt.'],
1953 ['value to test', 'test 3 failed', 'template with "%s" included', None, 'template with "value to test" included'],
1954 ['value to test', 'test 4 failed', 'template with value not included', None, 'template with value not included'],
1955 [None, 'initial value was None', 'template4value: %s', None, 'initial value was None'],
1956 [None, 'initial value was None', 'template4value: %%(abc)s', None, 'initial value was None']
1957 ]
1958 passed = True
1959 for test in tests:
1960 result = coalesce (
1961 value2test = test[0],
1962 return_instead = test[1],
1963 template4value = test[2],
1964 template4instead = test[3]
1965 )
1966 if result != test[4]:
1967 print("ERROR")
1968 print("coalesce: (%s, %s, %s, %s)" % (test[0], test[1], test[2], test[3]))
1969 print("expected:", test[4])
1970 print("received:", result)
1971 passed = False
1972
1973 if passed:
1974 print("passed")
1975 else:
1976 print("failed")
1977 return passed
1978
1980 print('testing capitalize() ...')
1981 success = True
1982 pairs = [
1983
1984 ['Boot', 'Boot', CAPS_FIRST_ONLY],
1985 ['boot', 'Boot', CAPS_FIRST_ONLY],
1986 ['booT', 'Boot', CAPS_FIRST_ONLY],
1987 ['BoOt', 'Boot', CAPS_FIRST_ONLY],
1988 ['boots-Schau', 'Boots-Schau', CAPS_WORDS],
1989 ['boots-sChau', 'Boots-Schau', CAPS_WORDS],
1990 ['boot camp', 'Boot Camp', CAPS_WORDS],
1991 ['fahrner-Kampe', 'Fahrner-Kampe', CAPS_NAMES],
1992 ['häkkönen', 'Häkkönen', CAPS_NAMES],
1993 ['McBurney', 'McBurney', CAPS_NAMES],
1994 ['mcBurney', 'McBurney', CAPS_NAMES],
1995 ['blumberg', 'Blumberg', CAPS_NAMES],
1996 ['roVsing', 'RoVsing', CAPS_NAMES],
1997 ['Özdemir', 'Özdemir', CAPS_NAMES],
1998 ['özdemir', 'Özdemir', CAPS_NAMES],
1999 ]
2000 for pair in pairs:
2001 result = capitalize(pair[0], pair[2])
2002 if result != pair[1]:
2003 success = False
2004 print('ERROR (caps mode %s): "%s" -> "%s", expected "%s"' % (pair[2], pair[0], result, pair[1]))
2005
2006 if success:
2007 print("... SUCCESS")
2008
2009 return success
2010
2012 print("testing import_module_from_directory()")
2013 path = sys.argv[1]
2014 name = sys.argv[2]
2015 try:
2016 mod = import_module_from_directory(module_path = path, module_name = name)
2017 except:
2018 print("module import failed, see log")
2019 return False
2020
2021 print("module import succeeded", mod)
2022 print(dir(mod))
2023 return True
2024
2026 print("testing mkdir(%s)" % sys.argv[2])
2027 mkdir(sys.argv[2], 0o0700)
2028
2039
2041 print("testing none_if()")
2042 print("-----------------")
2043 tests = [
2044 [None, None, None],
2045 ['a', 'a', None],
2046 ['a', 'b', 'a'],
2047 ['a', None, 'a'],
2048 [None, 'a', None],
2049 [1, 1, None],
2050 [1, 2, 1],
2051 [1, None, 1],
2052 [None, 1, None]
2053 ]
2054
2055 for test in tests:
2056 if none_if(value = test[0], none_equivalent = test[1]) != test[2]:
2057 print('ERROR: none_if(%s) returned [%s], expected [%s]' % (test[0], none_if(test[0], test[1]), test[2]))
2058
2059 return True
2060
2062 tests = [
2063 [True, 'Yes', 'Yes', 'Yes'],
2064 [False, 'OK', 'not OK', 'not OK']
2065 ]
2066 for test in tests:
2067 if bool2str(test[0], test[1], test[2]) != test[3]:
2068 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]))
2069
2070 return True
2071
2073
2074 print(bool2subst(True, 'True', 'False', 'is None'))
2075 print(bool2subst(False, 'True', 'False', 'is None'))
2076 print(bool2subst(None, 'True', 'False', 'is None'))
2077
2084
2086 print("testing size2str()")
2087 print("------------------")
2088 tests = [0, 1, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000, 1000000000000, 10000000000000]
2089 for test in tests:
2090 print(size2str(test))
2091
2093
2094 test = """
2095 second line\n
2096 3rd starts with tab \n
2097 4th with a space \n
2098
2099 6th
2100
2101 """
2102 print(unwrap(text = test, max_length = 25))
2103
2105 test = 'line 1\nline 2\nline 3'
2106
2107 print("wrap 5-6-7 initial 0, subsequent 0")
2108 print(wrap(test, 5))
2109 print()
2110 print(wrap(test, 6))
2111 print()
2112 print(wrap(test, 7))
2113 print("-------")
2114 input()
2115 print("wrap 5 initial 1-1-3, subsequent 1-3-1")
2116 print(wrap(test, 5, ' ', ' '))
2117 print()
2118 print(wrap(test, 5, ' ', ' '))
2119 print()
2120 print(wrap(test, 5, ' ', ' '))
2121 print("-------")
2122 input()
2123 print("wrap 6 initial 1-1-3, subsequent 1-3-1")
2124 print(wrap(test, 6, ' ', ' '))
2125 print()
2126 print(wrap(test, 6, ' ', ' '))
2127 print()
2128 print(wrap(test, 6, ' ', ' '))
2129 print("-------")
2130 input()
2131 print("wrap 7 initial 1-1-3, subsequent 1-3-1")
2132 print(wrap(test, 7, ' ', ' '))
2133 print()
2134 print(wrap(test, 7, ' ', ' '))
2135 print()
2136 print(wrap(test, 7, ' ', ' '))
2137
2139 print('md5 %s: %s' % (sys.argv[2], file2md5(sys.argv[2])))
2140 print('chunked md5 %s: %s' % (sys.argv[2], file2chunked_md5(sys.argv[2])))
2141
2144
2149
2151 tests = ['\\', '^', '~', '{', '}', '%', '&', '#', '$', '_', u_euro, 'abc\ndef\n\n1234']
2152 tests.append(' '.join(tests))
2153 for test in tests:
2154 print('%s:' % test, tex_escape_string(test))
2155
2156
2158 tests = ['\\', '^', '~', '{', '}', '%', '&', '#', '$', '_', u_euro, 'abc\ndef\n\n1234']
2159 tests.append(' '.join(tests))
2160 tests.append('C:\Windows\Programme\System 32\lala.txt')
2161 tests.extend([
2162 'should be identical',
2163 'text *some text* text',
2164 """A List
2165 ======
2166
2167 1. 1
2168 2. 2
2169
2170 3. ist-list
2171 1. more
2172 2. noch was ü
2173 #. nummer x"""
2174 ])
2175 for test in tests:
2176 print('==================================================')
2177 print('raw:')
2178 print(test)
2179 print('---------')
2180 print('ReST 2 LaTeX:')
2181 latex = rst2latex_snippet(test)
2182 print(latex)
2183 if latex.strip() == test.strip():
2184 print('=> identical')
2185 print('---------')
2186 print('tex_escape_string:')
2187 print(tex_escape_string(test))
2188 input()
2189
2190
2192 tests = [
2193 'one line, no embedded line breaks ',
2194 'one line\nwith embedded\nline\nbreaks\n '
2195 ]
2196 for test in tests:
2197 print('as list:')
2198 print(strip_trailing_empty_lines(text = test, eol='\n', return_list = True))
2199 print('as string:')
2200 print('>>>%s<<<' % strip_trailing_empty_lines(text = test, eol='\n', return_list = False))
2201 tests = [
2202 ['list', 'without', 'empty', 'trailing', 'lines'],
2203 ['list', 'with', 'empty', 'trailing', 'lines', '', ' ', '']
2204 ]
2205 for test in tests:
2206 print('as list:')
2207 print(strip_trailing_empty_lines(lines = test, eol = '\n', return_list = True))
2208 print('as string:')
2209 print(strip_trailing_empty_lines(lines = test, eol = '\n', return_list = False))
2210
2212 tests = [
2213 r'abc.exe',
2214 r'\abc.exe',
2215 r'c:\abc.exe',
2216 r'c:\d\abc.exe',
2217 r'/home/ncq/tmp.txt',
2218 r'~/tmp.txt',
2219 r'./tmp.txt',
2220 r'./.././tmp.txt',
2221 r'tmp.txt'
2222 ]
2223 for t in tests:
2224 print("[%s] -> [%s]" % (t, fname_stem(t)))
2225
2227 print(sys.argv[2], 'empty:', dir_is_empty(sys.argv[2]))
2228
2229
2231 d1 = {}
2232 d2 = {}
2233 d1[1] = 1
2234 d1[2] = 2
2235 d1[3] = 3
2236
2237 d1[5] = 5
2238
2239 d2[1] = 1
2240 d2[2] = None
2241
2242 d2[4] = 4
2243
2244
2245
2246 d1 = {1: 1, 2: 2}
2247 d2 = {1: 1, 2: 2}
2248
2249
2250 print(format_dict_like(d1, tabular = False))
2251 print(format_dict_like(d1, tabular = True))
2252
2253
2254
2275
2276
2278 rmdir('cx:\windows\system3__2xxxxxxxxxxxxx')
2279
2280
2282
2283 print(rm_dir_content('/tmp/user/1000/tmp'))
2284
2285
2287 tests = [
2288 ('', '', ''),
2289 ('a', 'a', ''),
2290 ('\.br\MICROCYTES+1\.br\SPHEROCYTES present\.br\POLYCHROMASIAmoderate\.br\\', '\.br\\', 'MICROCYTES+1\.br\SPHEROCYTES present\.br\POLYCHROMASIAmoderate\.br\\')
2291 ]
2292 for test in tests:
2293 text, prefix, expect = test
2294 result = strip_prefix(text, prefix)
2295 if result == expect:
2296 continue
2297 print('test failed:', test)
2298 print('result:', result)
2299
2301 tst = [
2302 ('123', 1),
2303 ('123', 2),
2304 ('123', 3),
2305 ('123', 4),
2306 ('', 1),
2307 ('1', 1),
2308 ('12', 1),
2309 ('', 2),
2310 ('1', 2),
2311 ('12', 2),
2312 ('123', 2)
2313 ]
2314 for txt, lng in tst:
2315 print('max', lng, 'of', txt, '=', shorten_text(txt, lng))
2316
2318 tests = [
2319 '/tmp/test.txt',
2320 '/tmp/ test.txt',
2321 '/tmp/ tes\\t.txt',
2322 'test'
2323 ]
2324 for test in tests:
2325 print (test, fname_sanitize(test))
2326
2327
2330
2331
2333 parts = enumerate_removable_partitions()
2334 for part_name in parts:
2335 part = parts[part_name]
2336 print(part['device'])
2337 print(part['partition'])
2338 if part['is_mounted']:
2339 print('%s@%s: %s on %s by %s @ %s (FS=%s: %s free of %s total)' % (
2340 part['type'],
2341 part['bus'],
2342 part['fs_label'],
2343 part['model'],
2344 part['vendor'],
2345 part['mountpoint'],
2346 part['fs_type'],
2347 part['bytes_free'],
2348 part['size_in_bytes']
2349 ))
2350 else:
2351 print('%s@%s: %s on %s by %s (not mounted)' % (
2352 part['type'],
2353 part['bus'],
2354 part['fs_label'],
2355 part['model'],
2356 part['vendor']
2357 ))
2358
2359
2361 for writer in enumerate_optical_writers():
2362 print('%s@%s: %s @ %s' % (
2363 writer['type'],
2364 writer['bus'],
2365 writer['model'],
2366 writer['device']
2367 ))
2368
2369
2371 print(sys.argv[2], '->', sys.argv[3])
2372 print(copy_tree_content(sys.argv[2], sys.argv[3]))
2373
2374
2377
2378
2380 dicts = [
2381 {'pkey': 1, 'value': 'a1'},
2382 {'pkey': 2, 'value': 'b2'},
2383 {'pkey': 3, 'value': 'c3'},
2384 {'pkey': 4, 'value': 'd4'},
2385 {'pkey': 5, 'value': 'd4'},
2386 {'pkey': 5, 'value': 'c5'},
2387 ]
2388 with open('x.txt', 'w', encoding = 'utf8') as f:
2389 f.write(dicts2table(dicts, left_margin=2, eol='\n', keys2ignore=None, show_only_changes=True, headers = ['d1', 'd2', 'd3', 'd4', 'd5', 'd6']))
2390
2391
2392
2393 test_coalesce()
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430