| Home | Trees | Indices | Help |
|
|---|
|
|
1 # -*- coding: utf-8 -*-
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 # std libs
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 # old:
31 import pickle, zlib
32 # docutils
33 du_core = None
34
35
36 # GNUmed libs
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 # CAPitalization modes:
45 ( CAPS_NONE, # don't touch it
46 CAPS_FIRST, # CAP first char, leave rest as is
47 CAPS_ALLCAPS, # CAP all chars
48 CAPS_WORDS, # CAP first char of every word
49 CAPS_NAMES, # CAP in a way suitable for names (tries to be smart)
50 CAPS_FIRST_ONLY # CAP first char, lowercase the rest
51 ) = range(6)
52
53
54 u_currency_pound = '\u00A3' # Pound sign
55 u_currency_sign = '\u00A4' # generic currency sign
56 u_currency_yen = '\u00A5' # Yen sign
57 u_right_double_angle_quote = '\u00AB' # <<
58 u_registered_trademark = '\u00AE'
59 u_plus_minus = '\u00B1'
60 u_superscript_one = '\u00B9' # ^1
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' # x
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' # triangular bullet (>)
72 u_ellipsis = '\u2026' # ...
73 u_euro = '\u20AC' # EURO sign
74 u_numero = '\u2116' # No. / # sign
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' # sigma
84 u_almost_equal_to = '\u2248' # approximately / nearly / roughly
85 u_corresponds_to = '\u2258'
86 u_infinity = '\u221E'
87 u_arrow2right_until_vertical_bar2 = '\u2b72' # -->|
88
89 u_diameter = '\u2300'
90 u_checkmark_crossed_out = '\u237B'
91 u_box_horiz_high = '\u23ba'
92 u_box_vert_left = '\u23b8'
93 u_box_vert_right = '\u23b9'
94
95 u_space_as_open_box = '\u2423'
96
97 u_box_horiz_single = '\u2500' # -
98 u_box_vert_light = '\u2502'
99 u_box_horiz_light_3dashes = '\u2504' # ...
100 u_box_vert_light_4dashes = '\u2506'
101 u_box_horiz_4dashes = '\u2508' # ....
102 u_box_T_right = '\u251c' # |-
103 u_box_T_left = '\u2524' # -|
104 u_box_T_down = '\u252c'
105 u_box_T_up = '\u2534'
106 u_box_plus = '\u253c'
107 u_box_top_double = '\u2550'
108 u_box_top_left_double_single = '\u2552'
109 u_box_top_right_double_single = '\u2555'
110 u_box_top_left_arc = '\u256d'
111 u_box_top_right_arc = '\u256e'
112 u_box_bottom_right_arc = '\u256f'
113 u_box_bottom_left_arc = '\u2570'
114 u_box_horiz_light_heavy = '\u257c'
115 u_box_horiz_heavy_light = '\u257e'
116
117 u_skull_and_crossbones = '\u2620'
118 u_caduceus = '\u2624'
119 u_frowning_face = '\u2639'
120 u_smiling_face = '\u263a'
121 u_black_heart = '\u2665'
122 u_female = '\u2640'
123 u_male = '\u2642'
124 u_male_female = '\u26a5'
125 u_chain = '\u26d3'
126
127 u_checkmark_thin = '\u2713'
128 u_checkmark_thick = '\u2714'
129 u_heavy_greek_cross = '\u271a'
130 u_arrow2right_thick = '\u2794'
131 u_writing_hand = '\u270d'
132 u_pencil_1 = '\u270e'
133 u_pencil_2 = '\u270f'
134 u_pencil_3 = '\u2710'
135 u_latin_cross = '\u271d'
136
137 u_arrow2right_until_black_diamond = '\u291e' # ->*
138
139 u_kanji_yen = '\u5186' # Yen kanji
140 u_replacement_character = '\ufffd'
141 u_link_symbol = '\u1f517'
142
143
144 _kB = 1024
145 _MB = 1024 * _kB
146 _GB = 1024 * _MB
147 _TB = 1024 * _GB
148 _PB = 1024 * _TB
149
150
151 _GM_TITLE_PREFIX = 'GMd'
152
153 #===========================================================================
155
156 print(".========================================================")
157 print("| Unhandled exception caught !")
158 print("| Type :", t)
159 print("| Value:", v)
160 print("`========================================================")
161 _log.critical('unhandled exception caught', exc_info = (t,v,tb))
162 sys.__excepthook__(t,v,tb)
163
164 #===========================================================================
165 # path level operations
166 #---------------------------------------------------------------------------
168 """Create directory.
169
170 - creates parent dirs if necessary
171 - does not fail if directory exists
172 <mode>: numeric, say 0o0700 for "-rwx------"
173 """
174 if os.path.isdir(directory):
175 if mode is None:
176 return True
177
178 changed = False
179 old_umask = os.umask(0)
180 try:
181 # does not WORK !
182 #os.chmod(directory, mode, follow_symlinks = (os.chmod in os.supports_follow_symlinks)) # can't do better
183 os.chmod(directory, mode)
184 changed = True
185 finally:
186 os.umask(old_umask)
187 return changed
188
189 if mode is None:
190 os.makedirs(directory)
191 return True
192
193 old_umask = os.umask(0)
194 try:
195 os.makedirs(directory, mode)
196 finally:
197 os.umask(old_umask)
198 return True
199
200 #---------------------------------------------------------------------------
202 #-------------------------------
203 def _on_rm_error(func, path, exc):
204 _log.error('error while shutil.rmtree(%s)', path, exc_info=exc)
205 return True
206 #-------------------------------
207 error_count = 0
208 try:
209 shutil.rmtree(directory, False, _on_rm_error)
210 except Exception:
211 _log.exception('cannot shutil.rmtree(%s)', directory)
212 error_count += 1
213 return error_count
214
215 #---------------------------------------------------------------------------
217 _log.debug('cleaning out [%s]', directory)
218 try:
219 items = os.listdir(directory)
220 except OSError:
221 return False
222 for item in items:
223 # attempt file/link removal and ignore (but log) errors
224 full_item = os.path.join(directory, item)
225 try:
226 os.remove(full_item)
227 except OSError: # as per the docs, this is a directory
228 _log.debug('[%s] seems to be a subdirectory', full_item)
229 errors = rmdir(full_item)
230 if errors > 0:
231 return False
232 except Exception:
233 _log.exception('cannot os.remove(%s) [a file or a link]', full_item)
234 return False
235
236 return True
237
238 #---------------------------------------------------------------------------
240 if base_dir is None:
241 base_dir = gmPaths().tmp_dir
242 else:
243 if not os.path.isdir(base_dir):
244 mkdir(base_dir, mode = 0o0700) # (invoking user only)
245 if prefix is None:
246 prefix = 'sndbx-'
247 return tempfile.mkdtemp(prefix = prefix, suffix = '', dir = base_dir)
248
249 #---------------------------------------------------------------------------
252
253 #---------------------------------------------------------------------------
255 # /home/user/dir/ -> dir
256 # /home/user/dir -> dir
257 return os.path.basename(os.path.normpath(directory)) # normpath removes trailing slashes if any
258
259 #---------------------------------------------------------------------------
261 try:
262 empty = (len(os.listdir(directory)) == 0)
263 except OSError as exc:
264 if exc.errno != 2: # no such file
265 raise
266 empty = None
267 return empty
268
269 #---------------------------------------------------------------------------
271 """Copy the *content* of <directory> *into* <target_directory>
272 which is created if need be.
273 """
274 assert (directory is not None), 'source <directory> should not be None'
275 _log.debug('copying content of [%s] into [%s]', directory, target_directory)
276 try:
277 items = os.listdir(directory)
278 except OSError:
279 return None
280
281 for item in items:
282 full_item = os.path.join(directory, item)
283 if os.path.isdir(full_item):
284 target_subdir = os.path.join(target_directory, item)
285 try:
286 shutil.copytree(full_item, target_subdir)
287 except Exception:
288 _log.exception('cannot copy subdir [%s]', full_item)
289 return None
290 else:
291 try:
292 shutil.copy2(full_item, target_directory)
293 except Exception:
294 _log.exception('cannot copy file [%s]', full_item)
295 return None
296
297 return target_directory
298
299 #---------------------------------------------------------------------------
300 #---------------------------------------------------------------------------
302 """This class provides the following paths:
303
304 .home_dir user home
305 .local_base_dir script installation dir
306 .working_dir current dir
307 .user_config_dir
308 .system_config_dir
309 .system_app_data_dir (not writable)
310 .tmp_dir instance-local
311 .user_tmp_dir user-local (NOT per instance)
312 .bytea_cache_dir caches downloaded BYTEA data
313 """
315 """Setup pathes.
316
317 <app_name> will default to (name of the script - .py)
318 """
319 try:
320 self.already_inited
321 return
322 except AttributeError:
323 pass
324
325 self.init_paths(app_name=app_name, wx=wx)
326 self.already_inited = True
327
328 #--------------------------------------
329 # public API
330 #--------------------------------------
332
333 if wx is None:
334 _log.debug('wxPython not available')
335 _log.debug('detecting paths directly')
336
337 if app_name is None:
338 app_name, ext = os.path.splitext(os.path.basename(sys.argv[0]))
339 _log.info('app name detected as [%s]', app_name)
340 else:
341 _log.info('app name passed in as [%s]', app_name)
342
343 # the user home, doesn't work in Wine so work around that
344 self.__home_dir = None
345
346 # where the main script (the "binary") is installed
347 if getattr(sys, 'frozen', False):
348 _log.info('frozen app, installed into temporary path')
349 # this would find the path of *THIS* file
350 #self.local_base_dir = os.path.dirname(__file__)
351 # while this is documented on the web, the ${_MEIPASS2} does not exist
352 #self.local_base_dir = os.environ.get('_MEIPASS2')
353 # this is what Martin Zibricky <mzibr.public@gmail.com> told us to use
354 # when asking about this on pyinstaller@googlegroups.com
355 #self.local_base_dir = sys._MEIPASS
356 # however, we are --onedir, so we should look at sys.executable
357 # as per the pyinstaller manual
358 self.local_base_dir = os.path.dirname(sys.executable)
359 else:
360 self.local_base_dir = os.path.abspath(os.path.dirname(sys.argv[0]))
361
362 # the current working dir at the OS
363 self.working_dir = os.path.abspath(os.curdir)
364
365 # user-specific config dir, usually below the home dir
366 mkdir(os.path.join(self.home_dir, '.%s' % app_name))
367 self.user_config_dir = os.path.join(self.home_dir, '.%s' % app_name)
368
369 # system-wide config dir, usually below /etc/ under UN*X
370 try:
371 self.system_config_dir = os.path.join('/etc', app_name)
372 except ValueError:
373 #self.system_config_dir = self.local_base_dir
374 self.system_config_dir = self.user_config_dir
375
376 # system-wide application data dir
377 try:
378 self.system_app_data_dir = os.path.join(sys.prefix, 'share', app_name)
379 except ValueError:
380 self.system_app_data_dir = self.local_base_dir
381
382 # temporary directory
383 try:
384 self.__tmp_dir_already_set
385 _log.debug('temp dir already set')
386 except AttributeError:
387 _log.info('temp file prefix: %s', tempfile.gettempprefix())
388 _log.info('initial (user level) temp dir: %s', tempfile.gettempdir())
389 bytes_free = shutil.disk_usage(tempfile.gettempdir()).free
390 _log.info('free disk space for temp dir: %s (%s bytes)', size2str(size = bytes_free), bytes_free)
391 # $TMP/gnumed-$USER/
392 self.user_tmp_dir = os.path.join(tempfile.gettempdir(), '%s-%s' % (app_name, getpass.getuser()))
393 mkdir(self.user_tmp_dir, 0o700)
394 _log.info('intermediate (app+user level) temp dir: %s', self.user_tmp_dir)
395 # $TMP/gnumed-$USER/g-$UNIQUE/
396 tempfile.tempdir = self.user_tmp_dir # tell mkdtemp about intermediate dir
397 self.tmp_dir = tempfile.mkdtemp(prefix = 'g-') # will set tempfile.tempdir as side effect
398 _log.info('final (app instance level) temp dir: %s', tempfile.gettempdir())
399
400 # BYTEA cache dir
401 cache_dir = os.path.join(self.user_tmp_dir, '.bytea_cache')
402 try:
403 stat = os.stat(cache_dir)
404 _log.warning('reusing BYTEA cache dir: %s', cache_dir)
405 _log.debug(stat)
406 except FileNotFoundError:
407 mkdir(cache_dir, mode = 0o0700)
408 self.bytea_cache_dir = cache_dir
409
410 self.__log_paths()
411 if wx is None:
412 return True
413
414 # retry with wxPython
415 _log.debug('re-detecting paths with wxPython')
416
417 std_paths = wx.StandardPaths.Get()
418 _log.info('wxPython app name is [%s]', wx.GetApp().GetAppName())
419
420 # user-specific config dir, usually below the home dir
421 mkdir(os.path.join(std_paths.GetUserConfigDir(), '.%s' % app_name))
422 self.user_config_dir = os.path.join(std_paths.GetUserConfigDir(), '.%s' % app_name)
423
424 # system-wide config dir, usually below /etc/ under UN*X
425 try:
426 tmp = std_paths.GetConfigDir()
427 if not tmp.endswith(app_name):
428 tmp = os.path.join(tmp, app_name)
429 self.system_config_dir = tmp
430 except ValueError:
431 # leave it at what it was from direct detection
432 pass
433
434 # system-wide application data dir
435 # Robin attests that the following doesn't always
436 # give sane values on Windows, so IFDEF it
437 if 'wxMSW' in wx.PlatformInfo:
438 _log.warning('this platform (wxMSW) sometimes returns a broken value for the system-wide application data dir')
439 else:
440 try:
441 self.system_app_data_dir = std_paths.GetDataDir()
442 except ValueError:
443 pass
444
445 self.__log_paths()
446 return True
447
448 #--------------------------------------
450 _log.debug('sys.argv[0]: %s', sys.argv[0])
451 _log.debug('sys.executable: %s', sys.executable)
452 _log.debug('sys._MEIPASS: %s', getattr(sys, '_MEIPASS', '<not found>'))
453 _log.debug('os.environ["_MEIPASS2"]: %s', os.environ.get('_MEIPASS2', '<not found>'))
454 _log.debug('__file__ : %s', __file__)
455 _log.debug('local application base dir: %s', self.local_base_dir)
456 _log.debug('current working dir: %s', self.working_dir)
457 _log.debug('user home dir: %s', self.home_dir)
458 _log.debug('user-specific config dir: %s', self.user_config_dir)
459 _log.debug('system-wide config dir: %s', self.system_config_dir)
460 _log.debug('system-wide application data dir: %s', self.system_app_data_dir)
461 _log.debug('temporary dir (user): %s', self.user_tmp_dir)
462 _log.debug('temporary dir (instance): %s', self.tmp_dir)
463 _log.debug('temporary dir (tempfile.tempdir): %s', tempfile.tempdir)
464 _log.debug('temporary dir (tempfile.gettempdir()): %s', tempfile.gettempdir())
465 _log.debug('BYTEA cache dir: %s', self.bytea_cache_dir)
466
467 #--------------------------------------
468 # properties
469 #--------------------------------------
471 if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)):
472 msg = '[%s:user_config_dir]: invalid path [%s]' % (self.__class__.__name__, path)
473 _log.error(msg)
474 raise ValueError(msg)
475 self.__user_config_dir = path
476
479
480 user_config_dir = property(_get_user_config_dir, _set_user_config_dir)
481 #--------------------------------------
483 if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)):
484 msg = '[%s:system_config_dir]: invalid path [%s]' % (self.__class__.__name__, path)
485 _log.error(msg)
486 raise ValueError(msg)
487 self.__system_config_dir = path
488
491
492 system_config_dir = property(_get_system_config_dir, _set_system_config_dir)
493 #--------------------------------------
495 if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)):
496 msg = '[%s:system_app_data_dir]: invalid path [%s]' % (self.__class__.__name__, path)
497 _log.error(msg)
498 raise ValueError(msg)
499 self.__system_app_data_dir = path
500
503
504 system_app_data_dir = property(_get_system_app_data_dir, _set_system_app_data_dir)
505 #--------------------------------------
508
510 if self.__home_dir is not None:
511 return self.__home_dir
512
513 tmp = os.path.expanduser('~')
514 if tmp == '~':
515 _log.error('this platform does not expand ~ properly')
516 try:
517 tmp = os.environ['USERPROFILE']
518 except KeyError:
519 _log.error('cannot access $USERPROFILE in environment')
520
521 if not (
522 os.access(tmp, os.R_OK)
523 and
524 os.access(tmp, os.X_OK)
525 and
526 os.access(tmp, os.W_OK)
527 ):
528 msg = '[%s:home_dir]: invalid path [%s]' % (self.__class__.__name__, tmp)
529 _log.error(msg)
530 raise ValueError(msg)
531
532 self.__home_dir = tmp
533 return self.__home_dir
534
535 home_dir = property(_get_home_dir, _set_home_dir)
536
537 #--------------------------------------
539 if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)):
540 msg = '[%s:tmp_dir]: invalid path [%s]' % (self.__class__.__name__, path)
541 _log.error(msg)
542 raise ValueError(msg)
543 _log.debug('previous temp dir: %s', tempfile.gettempdir())
544 self.__tmp_dir = path
545 tempfile.tempdir = self.__tmp_dir
546 _log.debug('new temp dir: %s', tempfile.gettempdir())
547 self.__tmp_dir_already_set = True
548
551
552 tmp_dir = property(_get_tmp_dir, _set_tmp_dir)
553
554 #===========================================================================
555 # file related tools
556 #---------------------------------------------------------------------------
557 -def recode_file(source_file=None, target_file=None, source_encoding='utf8', target_encoding=None, base_dir=None, error_mode='replace'):
558 if target_encoding is None:
559 return source_file
560 if target_encoding == source_encoding:
561 return source_file
562 if target_file is None:
563 target_file = get_unique_filename (
564 prefix = '%s-%s_%s-' % (fname_stem(source_file), source_encoding, target_encoding),
565 suffix = fname_extension(source_file, '.txt'),
566 tmp_dir = base_dir
567 )
568
569 _log.debug('[%s] -> [%s] (%s -> %s)', source_encoding, target_encoding, source_file, target_file)
570
571 in_file = io.open(source_file, mode = 'rt', encoding = source_encoding)
572 out_file = io.open(target_file, mode = 'wt', encoding = target_encoding, errors = error_mode)
573 for line in in_file:
574 out_file.write(line)
575 out_file.close()
576 in_file.close()
577
578 return target_file
579
580 #---------------------------------------------------------------------------
582 _log.debug('unzipping [%s] -> [%s]', archive_name, target_dir)
583 success = False
584 try:
585 with zipfile.ZipFile(archive_name) as archive:
586 archive.extractall(target_dir)
587 success = True
588 except Exception:
589 _log.exception('cannot unzip')
590 return False
591 if remove_archive:
592 remove_file(archive_name)
593 return success
594
595 #---------------------------------------------------------------------------
597 if not os.path.lexists(filename):
598 return True
599
600 # attempt file remove and ignore (but log) errors
601 try:
602 os.remove(filename)
603 return True
604
605 except Exception:
606 if log_error:
607 _log.exception('cannot os.remove(%s)', filename)
608
609 if force:
610 tmp_name = get_unique_filename(tmp_dir = fname_dir(filename))
611 _log.debug('attempting os.replace(%s -> %s)', filename, tmp_name)
612 try:
613 os.replace(filename, tmp_name)
614 return True
615
616 except Exception:
617 if log_error:
618 _log.exception('cannot os.remove(%s)', filename)
619
620 return False
621
622 #---------------------------------------------------------------------------
624 blocksize = 2**10 * 128 # 128k, since md5 uses 128 byte blocks
625 _log.debug('md5(%s): <%s> byte blocks', filename, blocksize)
626
627 f = io.open(filename, mode = 'rb')
628
629 md5 = hashlib.md5()
630 while True:
631 data = f.read(blocksize)
632 if not data:
633 break
634 md5.update(data)
635 f.close()
636
637 _log.debug('md5(%s): %s', filename, md5.hexdigest())
638
639 if return_hex:
640 return md5.hexdigest()
641 return md5.digest()
642
643 #---------------------------------------------------------------------------
645 _log.debug('chunked_md5(%s, chunk_size=%s bytes)', filename, chunk_size)
646 md5_concat = ''
647 f = open(filename, 'rb')
648 while True:
649 md5 = hashlib.md5()
650 data = f.read(chunk_size)
651 if not data:
652 break
653 md5.update(data)
654 md5_concat += md5.hexdigest()
655 f.close()
656 md5 = hashlib.md5()
657 md5.update(md5_concat)
658 hex_digest = md5.hexdigest()
659 _log.debug('md5("%s"): %s', md5_concat, hex_digest)
660 return hex_digest
661
662 #---------------------------------------------------------------------------
663 default_csv_reader_rest_key = 'list_of_values_of_unknown_fields'
664
666 try:
667 is_dict_reader = kwargs['dict']
668 del kwargs['dict']
669 except KeyError:
670 is_dict_reader = False
671
672 if is_dict_reader:
673 kwargs['restkey'] = default_csv_reader_rest_key
674 return csv.DictReader(unicode_csv_data, dialect=dialect, **kwargs)
675 return csv.reader(unicode_csv_data, dialect=dialect, **kwargs)
676
677
678
679
683
684 #def utf_8_encoder(unicode_csv_data):
685 # for line in unicode_csv_data:
686 # yield line.encode('utf-8')
687
689
690 # csv.py doesn't do Unicode; encode temporarily as UTF-8:
691 try:
692 is_dict_reader = kwargs['dict']
693 del kwargs['dict']
694 if is_dict_reader is not True:
695 raise KeyError
696 kwargs['restkey'] = default_csv_reader_rest_key
697 csv_reader = csv.DictReader(unicode2charset_encoder(unicode_csv_data), dialect=dialect, **kwargs)
698 except KeyError:
699 is_dict_reader = False
700 csv_reader = csv.reader(unicode2charset_encoder(unicode_csv_data), dialect=dialect, **kwargs)
701
702 for row in csv_reader:
703 # decode ENCODING back to Unicode, cell by cell:
704 if is_dict_reader:
705 for key in row.keys():
706 if key == default_csv_reader_rest_key:
707 old_data = row[key]
708 new_data = []
709 for val in old_data:
710 new_data.append(str(val, encoding))
711 row[key] = new_data
712 if default_csv_reader_rest_key not in csv_reader.fieldnames:
713 csv_reader.fieldnames.append(default_csv_reader_rest_key)
714 else:
715 row[key] = str(row[key], encoding)
716 yield row
717 else:
718 yield [ str(cell, encoding) for cell in row ]
719 #yield [str(cell, 'utf-8') for cell in row]
720
721 #---------------------------------------------------------------------------
723 """Normalizes unicode, removes non-alpha characters, converts spaces to underscores."""
724
725 dir_part, name_part = os.path.split(filename)
726 if name_part == '':
727 return filename
728
729 import unicodedata
730 name_part = unicodedata.normalize('NFKD', name_part)
731 # remove everything not in group []
732 name_part = regex.sub (
733 '[^.\w\s[\]()%§+-]',
734 '',
735 name_part,
736 flags = regex.UNICODE
737 ).strip()
738 # translate whitespace to underscore
739 name_part = regex.sub (
740 '\s+',
741 '_',
742 name_part,
743 flags = regex.UNICODE
744 )
745 return os.path.join(dir_part, name_part)
746
747 #---------------------------------------------------------------------------
749 """/home/user/dir/filename.ext -> filename"""
750 return os.path.splitext(os.path.basename(filename))[0]
751
752 #---------------------------------------------------------------------------
754 """/home/user/dir/filename.ext -> /home/user/dir/filename"""
755 return os.path.splitext(filename)[0]
756
757 #---------------------------------------------------------------------------
759 """ /home/user/dir/filename.ext -> .ext
760 '' or '.' -> fallback if any else ''
761 """
762 ext = os.path.splitext(filename)[1]
763 if ext.strip() not in ['.', '']:
764 return ext
765 if fallback is None:
766 return ''
767 return fallback
768
769 #---------------------------------------------------------------------------
773
774 #---------------------------------------------------------------------------
778
779 #---------------------------------------------------------------------------
781 """This function has a race condition between
782 its file.close()
783 and actually
784 using the filename in callers.
785
786 The file will NOT exist after calling this function.
787 """
788 if tmp_dir is None:
789 gmPaths() # setup tmp dir if necessary
790 else:
791 if (
792 not os.access(tmp_dir, os.F_OK)
793 or
794 not os.access(tmp_dir, os.X_OK | os.W_OK)
795 ):
796 _log.warning('cannot os.access() temporary dir [%s], using system default', tmp_dir)
797 tmp_dir = None
798
799 if include_timestamp:
800 ts = pydt.datetime.now().strftime('%m%d-%H%M%S-')
801 else:
802 ts = ''
803
804 kwargs = {
805 'dir': tmp_dir,
806 # make sure file gets deleted as soon as
807 # .close()d so we can "safely" open it again
808 'delete': True
809 }
810
811 if prefix is None:
812 kwargs['prefix'] = 'gm-%s' % ts
813 else:
814 kwargs['prefix'] = prefix + ts
815
816 if suffix in [None, '']:
817 kwargs['suffix'] = '.tmp'
818 else:
819 if not suffix.startswith('.'):
820 suffix = '.' + suffix
821 kwargs['suffix'] = suffix
822
823 f = tempfile.NamedTemporaryFile(**kwargs)
824 filename = f.name
825 f.close()
826
827 return filename
828
829 #---------------------------------------------------------------------------
831 import ctypes
832 #windows_create_symlink = ctypes.windll.kernel32.CreateSymbolicLinkW
833 kernel32 = ctype.WinDLL('kernel32', use_last_error = True)
834 windows_create_symlink = kernel32.CreateSymbolicLinkW
835 windows_create_symlink.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_uint32)
836 windows_create_symlink.restype = ctypes.c_ubyte
837 if os.path.isdir(physical_name):
838 flags = 1
839 else:
840 flags = 0
841 ret_code = windows_create_symlink(link_name, physical_name.replace('/', '\\'), flags)
842 _log.debug('ctypes.windll.kernel32.CreateSymbolicLinkW() [%s] exit code: %s', windows_create_symlink, ret_code)
843 if ret_code == 0:
844 raise ctypes.WinError()
845 return ret_code
846
847 #---------------------------------------------------------------------------
849
850 _log.debug('creating symlink (overwrite = %s):', overwrite)
851 _log.debug('link [%s] =>', link_name)
852 _log.debug('=> physical [%s]', physical_name)
853
854 if os.path.exists(link_name):
855 _log.debug('link exists')
856 if overwrite:
857 return True
858 return False
859
860 try:
861 os.symlink(physical_name, link_name)
862 except (AttributeError, NotImplementedError):
863 _log.debug('this Python does not have os.symlink(), trying via ctypes')
864 __make_symlink_on_windows(physical_name, link_name)
865 except PermissionError:
866 _log.exception('cannot create link')
867 return False
868 #except OSError:
869 # unpriviledged on Windows
870 return True
871
872 #===========================================================================
873 -def import_module_from_directory(module_path=None, module_name=None, always_remove_path=False):
874 """Import a module from any location."""
875
876 _log.debug('CWD: %s', os.getcwd())
877
878 remove_path = always_remove_path or False
879 if module_path not in sys.path:
880 _log.info('appending to sys.path: [%s]' % module_path)
881 sys.path.append(module_path)
882 remove_path = True
883
884 _log.debug('will remove import path: %s', remove_path)
885
886 if module_name.endswith('.py'):
887 module_name = module_name[:-3]
888
889 try:
890 module = __import__(module_name)
891 except Exception:
892 _log.exception('cannot __import__() module [%s] from [%s]' % (module_name, module_path))
893 while module_path in sys.path:
894 sys.path.remove(module_path)
895 raise
896
897 _log.info('imported module [%s] as [%s]' % (module_name, module))
898 if remove_path:
899 while module_path in sys.path:
900 sys.path.remove(module_path)
901
902 return module
903
904 #===========================================================================
905 # text related tools
906 #---------------------------------------------------------------------------
908 if size == 1:
909 return template % _('1 Byte')
910 if size < 10 * _kB:
911 return template % _('%s Bytes') % size
912 if size < _MB:
913 return template % '%.1f kB' % (float(size) / _kB)
914 if size < _GB:
915 return template % '%.1f MB' % (float(size) / _MB)
916 if size < _TB:
917 return template % '%.1f GB' % (float(size) / _GB)
918 if size < _PB:
919 return template % '%.1f TB' % (float(size) / _TB)
920 return template % '%.1f PB' % (float(size) / _PB)
921
922 #---------------------------------------------------------------------------
924 if boolean is None:
925 return none_return
926 if boolean:
927 return true_return
928 if not boolean:
929 return false_return
930 raise ValueError('bool2subst(): <boolean> arg must be either of True, False, None')
931
932 #---------------------------------------------------------------------------
934 return bool2subst (
935 boolean = bool(boolean),
936 true_return = true_str,
937 false_return = false_str
938 )
939
940 #---------------------------------------------------------------------------
942 """Modelled after the SQL NULLIF function."""
943 if value is None:
944 return None
945 if strip_string:
946 stripped = value.strip()
947 else:
948 stripped = value
949 if stripped == none_equivalent:
950 return None
951 return value
952
953 #---------------------------------------------------------------------------
954 -def coalesce(value2test=None, return_instead=None, template4value=None, template4instead=None, none_equivalents=None, function4value=None, value2return=None):
955 """Modelled after the SQL coalesce function.
956
957 To be used to simplify constructs like:
958
959 if value2test is None (or in none_equivalents):
960 value = (template4instead % return_instead) or return_instead
961 else:
962 value = (template4value % value2test) or value2test
963 print value
964
965 @param value2test: the value to be tested for <None>
966 @type value2test: any Python type, must have a __str__ method if template4value is not None
967 @param return_instead: the value to be returned if <initial> is None
968 @type return_instead: any Python type, must have a __str__ method if template4instead is not None
969 @param template4value: if <initial> is returned replace the value into this template, must contain one <%s>
970 @type template4value: string or None
971 @param template4instead: if <return_instead> is returned replace the value into this template, must contain one <%s>
972 @type template4instead: string or None
973
974 example:
975 function4value = ('strftime', '%Y-%m-%d')
976
977 Ideas:
978 - list of return_insteads: initial, [return_instead, template], [return_instead, template], [return_instead, template], template4value, ...
979 """
980 if none_equivalents is None:
981 none_equivalents = [None]
982
983 if value2test in none_equivalents:
984 if template4instead is None:
985 return return_instead
986 return template4instead % return_instead
987
988 # at this point, value2test was not equivalent to None
989
990 # 1) explicit value to return supplied ?
991 if value2return is not None:
992 return value2return
993
994 value2return = value2test
995 # 2) function supplied to be applied to the value ?
996 if function4value is not None:
997 funcname, args = function4value
998 func = getattr(value2test, funcname)
999 value2return = func(args)
1000
1001 # 3) template supplied to be applied to the value ?
1002 if template4value is None:
1003 return value2return
1004
1005 try:
1006 return template4value % value2return
1007 except TypeError:
1008 # except (TypeError, ValueError):
1009 # this should go, actually, only needed because "old" calls
1010 # to coalesce will still abuse template4value as explicit value2return,
1011 # relying on the replacement to above to fail
1012 if hasattr(_log, 'log_stack_trace'):
1013 _log.log_stack_trace(message = 'deprecated use of <template4value> for <value2return>')
1014 else:
1015 _log.error('deprecated use of <template4value> for <value2return>')
1016 _log.error(locals())
1017 return template4value
1018
1019 #---------------------------------------------------------------------------
1021 val = match_obj.group(0).lower()
1022 if val in ['von', 'van', 'de', 'la', 'l', 'der', 'den']: # FIXME: this needs to expand, configurable ?
1023 return val
1024 buf = list(val)
1025 buf[0] = buf[0].upper()
1026 for part in ['mac', 'mc', 'de', 'la']:
1027 if len(val) > len(part) and val[:len(part)] == part:
1028 buf[len(part)] = buf[len(part)].upper()
1029 return ''.join(buf)
1030
1031 #---------------------------------------------------------------------------
1033 """Capitalize the first character but leave the rest alone.
1034
1035 Note that we must be careful about the locale, this may
1036 have issues ! However, for UTF strings it should just work.
1037 """
1038 if (mode is None) or (mode == CAPS_NONE):
1039 return text
1040
1041 if len(text) == 0:
1042 return text
1043
1044 if mode == CAPS_FIRST:
1045 if len(text) == 1:
1046 return text[0].upper()
1047 return text[0].upper() + text[1:]
1048
1049 if mode == CAPS_ALLCAPS:
1050 return text.upper()
1051
1052 if mode == CAPS_FIRST_ONLY:
1053 # if len(text) == 1:
1054 # return text[0].upper()
1055 return text[0].upper() + text[1:].lower()
1056
1057 if mode == CAPS_WORDS:
1058 #return regex.sub(ur'(\w)(\w+)', lambda x: x.group(1).upper() + x.group(2).lower(), text)
1059 return regex.sub(r'(\w)(\w+)', lambda x: x.group(1).upper() + x.group(2).lower(), text)
1060
1061 if mode == CAPS_NAMES:
1062 #return regex.sub(r'\w+', __cap_name, text)
1063 return capitalize(text=text, mode=CAPS_FIRST) # until fixed
1064
1065 print("ERROR: invalid capitalization mode: [%s], leaving input as is" % mode)
1066 return text
1067
1068 #---------------------------------------------------------------------------
1070
1071 if isinstance(initial, decimal.Decimal):
1072 return True, initial
1073
1074 val = initial
1075
1076 # float ? -> to string first
1077 if type(val) == type(float(1.4)):
1078 val = str(val)
1079
1080 # string ? -> "," to "."
1081 if isinstance(val, str):
1082 val = val.replace(',', '.', 1)
1083 val = val.strip()
1084
1085 try:
1086 d = decimal.Decimal(val)
1087 return True, d
1088 except (TypeError, decimal.InvalidOperation):
1089 return False, val
1090
1091 #---------------------------------------------------------------------------
1093
1094 val = initial
1095
1096 # string ? -> "," to "."
1097 if isinstance(val, str):
1098 val = val.replace(',', '.', 1)
1099 val = val.strip()
1100
1101 try:
1102 int_val = int(val)
1103 except (TypeError, ValueError):
1104 _log.exception('int(%s) failed', val)
1105 return False, initial
1106
1107 if minval is not None:
1108 if int_val < minval:
1109 _log.debug('%s < min (%s)', val, minval)
1110 return False, initial
1111 if maxval is not None:
1112 if int_val > maxval:
1113 _log.debug('%s > max (%s)', val, maxval)
1114 return False, initial
1115
1116 return True, int_val
1117
1118 #---------------------------------------------------------------------------
1120 if remove_whitespace:
1121 text = text.lstrip()
1122 if not text.startswith(prefix):
1123 return text
1124
1125 text = text.replace(prefix, '', 1)
1126 if not remove_repeats:
1127 if remove_whitespace:
1128 return text.lstrip()
1129 return text
1130
1131 return strip_prefix(text, prefix, remove_repeats = True, remove_whitespace = remove_whitespace)
1132
1133 #---------------------------------------------------------------------------
1135 if title.startswith(_GM_TITLE_PREFIX):
1136 return title
1137 return '%s: %s' % (
1138 _GM_TITLE_PREFIX,
1139 title.lstrip()
1140 )
1141
1142 #---------------------------------------------------------------------------
1144 return strip_prefix(title, _GM_TITLE_PREFIX + ':', remove_repeats = True, remove_whitespace = True)
1145
1146 #---------------------------------------------------------------------------
1148 suffix_len = len(suffix)
1149 if remove_repeats:
1150 if remove_whitespace:
1151 while text.rstrip().endswith(suffix):
1152 text = text.rstrip()[:-suffix_len].rstrip()
1153 return text
1154 while text.endswith(suffix):
1155 text = text[:-suffix_len]
1156 return text
1157 if remove_whitespace:
1158 return text.rstrip()[:-suffix_len].rstrip()
1159 return text[:-suffix_len]
1160
1161 #---------------------------------------------------------------------------
1163 if lines is None:
1164 lines = text.split(eol)
1165
1166 while True:
1167 if lines[0].strip(eol).strip() != '':
1168 break
1169 lines = lines[1:]
1170
1171 if return_list:
1172 return lines
1173
1174 return eol.join(lines)
1175
1176 #---------------------------------------------------------------------------
1178 if lines is None:
1179 lines = text.split(eol)
1180
1181 while True:
1182 if lines[-1].strip(eol).strip() != '':
1183 break
1184 lines = lines[:-1]
1185
1186 if return_list:
1187 return lines
1188
1189 return eol.join(lines)
1190
1191 #---------------------------------------------------------------------------
1193 return strip_trailing_empty_lines (
1194 lines = strip_leading_empty_lines(lines = lines, text = text, eol = eol, return_list = True),
1195 text = None,
1196 eol = eol,
1197 return_list = return_list
1198 )
1199
1200 #---------------------------------------------------------------------------
1201 -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):
1202
1203 if len(lines) == 0:
1204 return ''
1205
1206 if strip_leading_empty_lines:
1207 lines = strip_leading_empty_lines(lines = lines, eol = eol, return_list = True)
1208
1209 if strip_trailing_empty_lines:
1210 lines = strip_trailing_empty_lines(lines = lines, eol = eol, return_list = True)
1211
1212 if strip_trailing_whitespace:
1213 lines = [ l.rstrip() for l in lines ]
1214
1215 if max_line_width is not None:
1216 wrapped_lines = []
1217 for l in lines:
1218 wrapped_lines.extend(wrap(l, max_line_width).split('\n'))
1219 lines = wrapped_lines
1220
1221 indented_lines = [initial_indent + lines[0]]
1222 indented_lines.extend([ subsequent_indent + l for l in lines[1:] ])
1223
1224 return eol.join(indented_lines)
1225
1226 #---------------------------------------------------------------------------
1228 """A word-wrap function that preserves existing line breaks
1229 and most spaces in the text. Expects that existing line
1230 breaks are posix newlines (\n).
1231 """
1232 if width is None:
1233 return text
1234
1235 wrapped = initial_indent + functools.reduce (
1236 lambda line, word, width=width: '%s%s%s' % (
1237 line,
1238 ' \n'[(len(line) - line.rfind('\n') - 1 + len(word.split('\n',1)[0]) >= width)],
1239 word
1240 ),
1241 text.split(' ')
1242 )
1243 if subsequent_indent != '':
1244 wrapped = ('\n%s' % subsequent_indent).join(wrapped.split('\n'))
1245 if eol != '\n':
1246 wrapped = wrapped.replace('\n', eol)
1247 return wrapped
1248
1249 #---------------------------------------------------------------------------
1250 -def unwrap(text=None, max_length=None, strip_whitespace=True, remove_empty_lines=True, line_separator = ' // '):
1251
1252 text = text.replace('\r', '')
1253 lines = text.split('\n')
1254 text = ''
1255 for line in lines:
1256
1257 if strip_whitespace:
1258 line = line.strip().strip('\t').strip()
1259
1260 if remove_empty_lines:
1261 if line == '':
1262 continue
1263
1264 text += ('%s%s' % (line, line_separator))
1265
1266 text = text.rstrip(line_separator)
1267
1268 if max_length is not None:
1269 text = text[:max_length]
1270
1271 text = text.rstrip(line_separator)
1272
1273 return text
1274
1275 #---------------------------------------------------------------------------
1277
1278 if len(text) <= max_length:
1279 return text
1280
1281 return text[:max_length-1] + u_ellipsis
1282
1283 #---------------------------------------------------------------------------
1284 -def shorten_words_in_line(text=None, max_length=None, min_word_length=None, ignore_numbers=True, ellipsis=u_ellipsis):
1285 if text is None:
1286 return None
1287 if max_length is None:
1288 max_length = len(text)
1289 else:
1290 if len(text) <= max_length:
1291 return text
1292 old_words = regex.split('\s+', text, flags = regex.UNICODE)
1293 no_old_words = len(old_words)
1294 max_word_length = max(min_word_length, (max_length // no_old_words))
1295 words = []
1296 for word in old_words:
1297 if len(word) <= max_word_length:
1298 words.append(word)
1299 continue
1300 if ignore_numbers:
1301 tmp = word.replace('-', '').replace('+', '').replace('.', '').replace(',', '').replace('/', '').replace('&', '').replace('*', '')
1302 if tmp.isdigit():
1303 words.append(word)
1304 continue
1305 words.append(word[:max_word_length] + ellipsis)
1306 return ' '.join(words)
1307
1308 #---------------------------------------------------------------------------
1312
1313 #---------------------------------------------------------------------------
1314 -def tex_escape_string(text=None, replace_known_unicode=True, replace_eol=False, keep_visual_eol=False):
1315 """Check for special TeX characters and transform them.
1316
1317 replace_eol:
1318 replaces "\n" with "\\newline"
1319 keep_visual_eol:
1320 replaces "\n" with "\\newline \n" such that
1321 both LaTeX will know to place a line break
1322 at this point as well as the visual formatting
1323 is preserved in the LaTeX source (think multi-
1324 row table cells)
1325 """
1326 text = text.replace('\\', '\\textbackslash') # requires \usepackage{textcomp} in LaTeX source
1327 text = text.replace('^', '\\textasciicircum')
1328 text = text.replace('~', '\\textasciitilde')
1329
1330 text = text.replace('{', '\\{')
1331 text = text.replace('}', '\\}')
1332 text = text.replace('%', '\\%')
1333 text = text.replace('&', '\\&')
1334 text = text.replace('#', '\\#')
1335 text = text.replace('$', '\\$')
1336 text = text.replace('_', '\\_')
1337 if replace_eol:
1338 if keep_visual_eol:
1339 text = text.replace('\n', '\\newline \n')
1340 else:
1341 text = text.replace('\n', '\\newline ')
1342
1343 if replace_known_unicode:
1344 # this should NOT be replaced for Xe(La)Tex
1345 text = text.replace(u_euro, '\\EUR') # requires \usepackage{textcomp} in LaTeX source
1346 text = text.replace(u_sum, '$\\Sigma$')
1347
1348 return text
1349
1350 #---------------------------------------------------------------------------
1352 global du_core
1353 if du_core is None:
1354 try:
1355 from docutils import core as du_core
1356 except ImportError:
1357 _log.warning('cannot turn ReST into LaTeX: docutils not installed')
1358 return tex_escape_string(text = rst_text)
1359
1360 parts = du_core.publish_parts (
1361 source = rst_text.replace('\\', '\\\\'),
1362 source_path = '<internal>',
1363 writer_name = 'latex',
1364 #destination_path = '/path/to/LaTeX-template/for/calculating/relative/links/template.tex',
1365 settings_overrides = {
1366 'input_encoding': 'unicode' # un-encoded unicode
1367 },
1368 enable_exit_status = True # how to use ?
1369 )
1370 return parts['body']
1371
1372 #---------------------------------------------------------------------------
1374 global du_core
1375 if du_core is None:
1376 try:
1377 from docutils import core as du_core
1378 except ImportError:
1379 _log.warning('cannot turn ReST into HTML: docutils not installed')
1380 return html_escape_string(text = rst_text, replace_eol=False, keep_visual_eol=False)
1381
1382 parts = du_core.publish_parts (
1383 source = rst_text.replace('\\', '\\\\'),
1384 source_path = '<internal>',
1385 writer_name = 'latex',
1386 #destination_path = '/path/to/LaTeX-template/for/calculating/relative/links/template.tex',
1387 settings_overrides = {
1388 'input_encoding': 'unicode' # un-encoded unicode
1389 },
1390 enable_exit_status = True # how to use ?
1391 )
1392 return parts['body']
1393
1394 #---------------------------------------------------------------------------
1396 # a web search did not reveal anything else for Xe(La)Tex
1397 # as opposed to LaTeX, except true unicode chars
1398 return tex_escape_string(text = text, replace_known_unicode = False)
1399
1400 #---------------------------------------------------------------------------
1401 __html_escape_table = {
1402 "&": "&",
1403 '"': """,
1404 "'": "'",
1405 ">": ">",
1406 "<": "<",
1407 }
1408
1410 text = ''.join(__html_escape_table.get(char, char) for char in text)
1411 if replace_eol:
1412 if keep_visual_eol:
1413 text = text.replace('\n', '<br>\n')
1414 else:
1415 text = text.replace('\n', '<br>')
1416 return text
1417
1418 #---------------------------------------------------------------------------
1421
1422 #---------------------------------------------------------------------------
1424 if isinstance(obj, pydt.datetime):
1425 return obj.isoformat()
1426 raise TypeError('cannot json_serialize(%s)' % type(obj))
1427
1428 #---------------------------------------------------------------------------
1429 #---------------------------------------------------------------------------
1431 _log.info('comparing dict-likes: %s[%s] vs %s[%s]', coalesce(title1, '', '"%s" '), type(d1), coalesce(title2, '', '"%s" '), type(d2))
1432 try:
1433 d1 = dict(d1)
1434 except TypeError:
1435 pass
1436 try:
1437 d2 = dict(d2)
1438 except TypeError:
1439 pass
1440 keys_d1 = frozenset(d1.keys())
1441 keys_d2 = frozenset(d2.keys())
1442 different = False
1443 if len(keys_d1) != len(keys_d2):
1444 _log.info('different number of keys: %s vs %s', len(keys_d1), len(keys_d2))
1445 different = True
1446 for key in keys_d1:
1447 if key in keys_d2:
1448 if type(d1[key]) != type(d2[key]):
1449 _log.info('%25.25s: type(dict1) = %s = >>>%s<<<' % (key, type(d1[key]), d1[key]))
1450 _log.info('%25.25s type(dict2) = %s = >>>%s<<<' % ('', type(d2[key]), d2[key]))
1451 different = True
1452 continue
1453 if d1[key] == d2[key]:
1454 _log.info('%25.25s: both = >>>%s<<<' % (key, d1[key]))
1455 else:
1456 _log.info('%25.25s: dict1 = >>>%s<<<' % (key, d1[key]))
1457 _log.info('%25.25s dict2 = >>>%s<<<' % ('', d2[key]))
1458 different = True
1459 else:
1460 _log.info('%25.25s: %50.50s | <MISSING>' % (key, '>>>%s<<<' % d1[key]))
1461 different = True
1462 for key in keys_d2:
1463 if key in keys_d1:
1464 continue
1465 _log.info('%25.25s: %50.50s | %.50s' % (key, '<MISSING>', '>>>%s<<<' % d2[key]))
1466 different = True
1467 if different:
1468 _log.info('dict-likes appear to be different from each other')
1469 return False
1470 _log.info('dict-likes appear equal to each other')
1471 return True
1472
1473 #---------------------------------------------------------------------------
1474 -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):
1475
1476 _log.info('comparing dict-likes: %s[%s] vs %s[%s]', coalesce(title_left, '', '"%s" '), type(d1), coalesce(title_right, '', '"%s" '), type(d2))
1477 append_type = False
1478 if None not in [title_left, title_right]:
1479 append_type = True
1480 type_left = type(d1)
1481 type_right = type(d2)
1482 if title_left is None:
1483 title_left = '%s' % type_left
1484 if title_right is None:
1485 title_right = '%s' % type_right
1486
1487 try: d1 = dict(d1)
1488 except TypeError: pass
1489 try: d2 = dict(d2)
1490 except TypeError: pass
1491 keys_d1 = d1.keys()
1492 keys_d2 = d2.keys()
1493 data = {}
1494 for key in keys_d1:
1495 data[key] = [d1[key], ' ']
1496 if key in d2:
1497 data[key][1] = d2[key]
1498 for key in keys_d2:
1499 if key in keys_d1:
1500 continue
1501 data[key] = [' ', d2[key]]
1502 max1 = max([ len('%s' % k) for k in keys_d1 ])
1503 max2 = max([ len('%s' % k) for k in keys_d2 ])
1504 max_len = max(max1, max2, len(_('<type>')))
1505 max_key_len_str = '%' + '%s.%s' % (max_len, max_len) + 's'
1506 max1 = max([ len('%s' % d1[k]) for k in keys_d1 ])
1507 max2 = max([ len('%s' % d2[k]) for k in keys_d2 ])
1508 max_data_len = min(max(max1, max2), 100)
1509 max_data_len_str = '%' + '%s.%s' % (max_data_len, max_data_len) + 's'
1510 diff_indicator_len_str = '%' + '%s.%s' % (len(difference_indicator), len(difference_indicator)) + 's'
1511 line_template = (' ' * left_margin) + diff_indicator_len_str + max_key_len_str + key_delim + max_data_len_str + data_delim + '%s'
1512
1513 lines = []
1514 # debugging:
1515 #lines.append(u' (40 regular spaces)')
1516 #lines.append((u' ' * 40) + u"(u' ' * 40)")
1517 #lines.append((u'%40.40s' % u'') + u"(u'%40.40s' % u'')")
1518 #lines.append((u'%40.40s' % u' ') + u"(u'%40.40s' % u' ')")
1519 #lines.append((u'%40.40s' % u'.') + u"(u'%40.40s' % u'.')")
1520 #lines.append(line_template)
1521 lines.append(line_template % ('', '', title_left, title_right))
1522 if append_type:
1523 lines.append(line_template % ('', _('<type>'), type_left, type_right))
1524
1525 if ignore_diff_in_keys is None:
1526 ignore_diff_in_keys = []
1527
1528 for key in keys_d1:
1529 append_type = False
1530 txt_left_col = '%s' % d1[key]
1531 try:
1532 txt_right_col = '%s' % d2[key]
1533 if type(d1[key]) != type(d2[key]):
1534 append_type = True
1535 except KeyError:
1536 txt_right_col = missing_string
1537 lines.append(line_template % (
1538 bool2subst (
1539 ((txt_left_col == txt_right_col) or (key in ignore_diff_in_keys)),
1540 '',
1541 difference_indicator
1542 ),
1543 key,
1544 shorten_text(txt_left_col, max_data_len),
1545 shorten_text(txt_right_col, max_data_len)
1546 ))
1547 if append_type:
1548 lines.append(line_template % (
1549 '',
1550 _('<type>'),
1551 shorten_text('%s' % type(d1[key]), max_data_len),
1552 shorten_text('%s' % type(d2[key]), max_data_len)
1553 ))
1554
1555 for key in keys_d2:
1556 if key in keys_d1:
1557 continue
1558 lines.append(line_template % (
1559 bool2subst((key in ignore_diff_in_keys), '', difference_indicator),
1560 key,
1561 shorten_text(missing_string, max_data_len),
1562 shorten_text('%s' % d2[key], max_data_len)
1563 ))
1564
1565 return lines
1566
1567 #---------------------------------------------------------------------------
1568 -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', values2ignore=None):
1569 if values2ignore is None:
1570 values2ignore = []
1571 if template is not None:
1572 # all keys in template better exist in d
1573 try:
1574 return template % d
1575 except KeyError:
1576 # or else
1577 _log.exception('template contains %%()s key(s) which do not exist in data dict')
1578 # try to extend dict <d> to contain all required keys,
1579 # for that to work <relevant_keys> better list all
1580 # keys used in <template>
1581 if relevant_keys is not None:
1582 for key in relevant_keys:
1583 try:
1584 d[key]
1585 except KeyError:
1586 d[key] = missing_key_template % {'key': key}
1587 return template % d
1588
1589 if relevant_keys is None:
1590 relevant_keys = list(d.keys())
1591 lines = []
1592 if value_delimiters is None:
1593 delim_left = ''
1594 delim_right = ''
1595 else:
1596 delim_left, delim_right = value_delimiters
1597 if tabular:
1598 max_len = max([ len('%s' % k) for k in relevant_keys ])
1599 max_len_str = '%s.%s' % (max_len, max_len)
1600 line_template = (' ' * left_margin) + '%' + max_len_str + ('s: %s%%s%s' % (delim_left, delim_right))
1601 else:
1602 line_template = (' ' * left_margin) + '%%s: %s%%s%s' % (delim_left, delim_right)
1603 for key in relevant_keys:
1604 try:
1605 val = d[key]
1606 except KeyError:
1607 continue
1608 if val not in values2ignore:
1609 lines.append(line_template % (key, val))
1610 if eol is None:
1611 return lines
1612 return eol.join(lines)
1613
1614 #---------------------------------------------------------------------------
1615 -def dicts2table(dict_list, left_margin=0, eol='\n', keys2ignore=None, headers=None, show_only_changes=False, equality_value='<=>', date_format=None): #, relevant_keys=None, template=None
1616
1617 keys2show = []
1618 dict_max_size = {}
1619 max_row_label_size = 0
1620 if keys2ignore is None:
1621 keys2ignore = []
1622
1623 # extract keys from all dicts
1624 for d in dict_list:
1625 dict_max_size[id(d)] = 0
1626 for key in d.keys():
1627 if key in keys2ignore:
1628 continue
1629 dict_max_size[id(d)] = max(dict_max_size[id(d)], len('%s' % d[key]))
1630 if key in keys2show:
1631 continue
1632 keys2show.append(key)
1633 max_row_label_size = max(max_row_label_size, len('%s' % key))
1634
1635 # pivot data into dict of lists per line
1636 lines = { k: [] for k in keys2show }
1637 prev_vals = {}
1638 for d in dict_list:
1639 if show_only_changes:
1640 max_size = max(dict_max_size[id(d)], len(equality_value))
1641 else:
1642 max_size = dict_max_size[id(d)]
1643 max_len_str = '%s.%s' % (max_size, max_size)
1644 field_template = ' %' + max_len_str + 's'
1645 for key in keys2show:
1646 try:
1647 val = d[key]
1648 except KeyError:
1649 lines[key].append(field_template % _('<missing>'))
1650 continue
1651 if isinstance(val, pydt.datetime):
1652 if date_format is not None:
1653 val = val.strftime(date_format)
1654 lines[key].append(field_template % val)
1655 if show_only_changes:
1656 if key not in prev_vals:
1657 prev_vals[key] = '%s' % lines[key][-1]
1658 continue
1659 if lines[key][-1] != prev_vals[key]:
1660 prev_vals[key] = '%s' % lines[key][-1]
1661 continue
1662 lines[key][-1] = field_template % equality_value
1663
1664 # format data into table
1665 table_lines = []
1666 max_len_str = '%s.%s' % (max_row_label_size, max_row_label_size)
1667 row_label_template = '%' + max_len_str + 's'
1668 for key in lines:
1669 line = (' ' * left_margin) + row_label_template % key + '|'
1670 line += '|'.join(lines[key])
1671 table_lines.append(line)
1672
1673 # add header line if any
1674 if headers is not None:
1675 header_line1 = (' ' * left_margin) + row_label_template % ''
1676 header_line2 = (' ' * left_margin) + u_box_horiz_single * (max_row_label_size)
1677 header_sizes = [ max(dict_max_size[dict_id], len(equality_value)) for dict_id in dict_max_size ]
1678 for idx in range(len(headers)):
1679 max_len_str = '%s.%s' % (header_sizes[idx], header_sizes[idx])
1680 header_template = '%' + max_len_str + 's'
1681 header_line1 += '| '
1682 header_line1 += header_template % headers[idx]
1683 header_line2 += '%s%s' % (u_box_plus, u_box_horiz_single)
1684 header_line2 += u_box_horiz_single * header_sizes[idx]
1685 table_lines.insert(0, header_line2)
1686 table_lines.insert(0, header_line1)
1687
1688 if eol is None:
1689 return table_lines
1690
1691 return ('|' + eol).join(table_lines) + '|' + eol
1692
1693 #---------------------------------------------------------------------------
1695 for key in required_keys:
1696 try:
1697 d[key]
1698 except KeyError:
1699 if missing_key_template is None:
1700 d[key] = None
1701 else:
1702 d[key] = missing_key_template % {'key': key}
1703 return d
1704
1705 #---------------------------------------------------------------------------
1707 try:
1708 import pyudev
1709 import psutil
1710 except ImportError:
1711 _log.error('pyudev and/or psutil not installed')
1712 return {}
1713
1714 removable_partitions = {}
1715 ctxt = pyudev.Context()
1716 removable_devices = [ dev for dev in ctxt.list_devices(subsystem='block', DEVTYPE='disk') if dev.attributes.get('removable') == b'1' ]
1717 all_mounted_partitions = { part.device: part for part in psutil.disk_partitions() }
1718 for device in removable_devices:
1719 _log.debug('removable device: %s', device.properties['ID_MODEL'])
1720 partitions_on_removable_device = {
1721 part.device_node: {
1722 'type': device.properties['ID_TYPE'],
1723 'bus': device.properties['ID_BUS'],
1724 'device': device.properties['DEVNAME'],
1725 'partition': part.properties['DEVNAME'],
1726 'vendor': part.properties['ID_VENDOR'],
1727 'model': part.properties['ID_MODEL'],
1728 'fs_label': part.properties['ID_FS_LABEL'],
1729 'is_mounted': False,
1730 'mountpoint': None,
1731 'fs_type': None,
1732 'size_in_bytes': -1,
1733 'bytes_free': 0
1734 } for part in ctxt.list_devices(subsystem='block', DEVTYPE='partition', parent=device)
1735 }
1736 for part in partitions_on_removable_device:
1737 try:
1738 partitions_on_removable_device[part]['mountpoint'] = all_mounted_partitions[part].mountpoint
1739 partitions_on_removable_device[part]['is_mounted'] = True
1740 partitions_on_removable_device[part]['fs_type'] = all_mounted_partitions[part].fstype
1741 du = shutil.disk_usage(all_mounted_partitions[part].mountpoint)
1742 partitions_on_removable_device[part]['size_in_bytes'] = du.total
1743 partitions_on_removable_device[part]['bytes_free'] = du.free
1744 except KeyError:
1745 pass # not mounted
1746 removable_partitions.update(partitions_on_removable_device)
1747 return removable_partitions
1748
1749 # debugging:
1750 #ctxt = pyudev.Context()
1751 #for dev in ctxt.list_devices(subsystem='block', DEVTYPE='disk'):# if dev.attributes.get('removable') == b'1':
1752 # for a in dev.attributes.available_attributes:
1753 # print(a, dev.attributes.get(a))
1754 # for key, value in dev.items():
1755 # print('{key}={value}'.format(key=key, value=value))
1756 # print('---------------------------')
1757
1758 #---------------------------------------------------------------------------
1760 try:
1761 import pyudev
1762 except ImportError:
1763 _log.error('pyudev not installed')
1764 return []
1765
1766 optical_writers = []
1767 ctxt = pyudev.Context()
1768 for dev in [ dev for dev in ctxt.list_devices(subsystem='block', DEVTYPE='disk') if dev.properties.get('ID_CDROM_CD_RW', None) == '1' ]:
1769 optical_writers.append ({
1770 'type': dev.properties['ID_TYPE'],
1771 'bus': dev.properties['ID_BUS'],
1772 'device': dev.properties['DEVNAME'],
1773 'model': dev.properties['ID_MODEL']
1774 })
1775 return optical_writers
1776
1777 #---------------------------------------------------------------------------
1778 #---------------------------------------------------------------------------
1780 """Obtains entry from standard input.
1781
1782 prompt: Prompt text to display in standard output
1783 default: Default value (for user to press enter only)
1784 CTRL-C: aborts and returns None
1785 """
1786 if prompt is None:
1787 msg = '(CTRL-C aborts)'
1788 else:
1789 msg = '%s (CTRL-C aborts)' % prompt
1790
1791 if default is None:
1792 msg = msg + ': '
1793 else:
1794 msg = '%s [%s]: ' % (msg, default)
1795
1796 try:
1797 usr_input = input(msg)
1798 except KeyboardInterrupt:
1799 return None
1800
1801 if usr_input == '':
1802 return default
1803
1804 return usr_input
1805
1806 #===========================================================================
1807 # image handling tools
1808 #---------------------------------------------------------------------------
1809 # builtin (ugly but tried and true) fallback icon
1810 __icon_serpent = \
1811 """x\xdae\x8f\xb1\x0e\x83 \x10\x86w\x9f\xe2\x92\x1blb\xf2\x07\x96\xeaH:0\xd6\
1812 \xc1\x85\xd5\x98N5\xa5\xef?\xf5N\xd0\x8a\xdcA\xc2\xf7qw\x84\xdb\xfa\xb5\xcd\
1813 \xd4\xda;\xc9\x1a\xc8\xb6\xcd<\xb5\xa0\x85\x1e\xeb\xbc\xbc7b!\xf6\xdeHl\x1c\
1814 \x94\x073\xec<*\xf7\xbe\xf7\x99\x9d\xb21~\xe7.\xf5\x1f\x1c\xd3\xbdVlL\xc2\
1815 \xcf\xf8ye\xd0\x00\x90\x0etH \x84\x80B\xaa\x8a\x88\x85\xc4(U\x9d$\xfeR;\xc5J\
1816 \xa6\x01\xbbt9\xceR\xc8\x81e_$\x98\xb9\x9c\xa9\x8d,y\xa9t\xc8\xcf\x152\xe0x\
1817 \xe9$\xf5\x07\x95\x0cD\x95t:\xb1\x92\xae\x9cI\xa8~\x84\x1f\xe0\xa3ec"""
1818
1820
1821 paths = gmPaths(app_name = 'gnumed', wx = wx)
1822
1823 candidates = [
1824 os.path.join(paths.system_app_data_dir, 'bitmaps', 'gm_icon-serpent_and_gnu.png'),
1825 os.path.join(paths.local_base_dir, 'bitmaps', 'gm_icon-serpent_and_gnu.png'),
1826 os.path.join(paths.system_app_data_dir, 'bitmaps', 'serpent.png'),
1827 os.path.join(paths.local_base_dir, 'bitmaps', 'serpent.png')
1828 ]
1829
1830 found_as = None
1831 for candidate in candidates:
1832 try:
1833 open(candidate, 'r').close()
1834 found_as = candidate
1835 break
1836 except IOError:
1837 _log.debug('icon not found in [%s]', candidate)
1838
1839 if found_as is None:
1840 _log.warning('no icon file found, falling back to builtin (ugly) icon')
1841 icon_bmp_data = wx.BitmapFromXPMData(pickle.loads(zlib.decompress(__icon_serpent)))
1842 icon.CopyFromBitmap(icon_bmp_data)
1843 else:
1844 _log.debug('icon found in [%s]', found_as)
1845 icon = wx.Icon()
1846 try:
1847 icon.LoadFile(found_as, wx.BITMAP_TYPE_ANY) #_PNG
1848 except AttributeError:
1849 _log.exception("this platform doesn't support wx.Icon().LoadFile()")
1850
1851 return icon
1852
1853 #---------------------------------------------------------------------------
1855 assert (not ((text is None) and (filename is None))), 'either <text> or <filename> must be specified'
1856
1857 try:
1858 import pyqrcode
1859 except ImportError:
1860 _log.exception('cannot import <pyqrcode>')
1861 return None
1862 if text is None:
1863 with io.open(filename, mode = 'rt', encoding = 'utf8') as input_file:
1864 text = input_file.read()
1865 if qr_filename is None:
1866 if filename is None:
1867 qr_filename = get_unique_filename(prefix = 'gm-qr-', suffix = '.png')
1868 else:
1869 qr_filename = get_unique_filename (
1870 prefix = fname_stem(filename) + '-',
1871 suffix = fname_extension(filename) + '.png'
1872 )
1873 _log.debug('[%s] -> [%s]', filename, qr_filename)
1874 qr = pyqrcode.create(text, encoding = 'utf8')
1875 if verbose:
1876 print('input file:', filename)
1877 print('output file:', qr_filename)
1878 print('text to encode:', text)
1879 print(qr.terminal())
1880 qr.png(qr_filename, quiet_zone = 1)
1881 return qr_filename
1882
1883 #===========================================================================
1884 # main
1885 #---------------------------------------------------------------------------
1886 if __name__ == '__main__':
1887
1888 if len(sys.argv) < 2:
1889 sys.exit()
1890
1891 if sys.argv[1] != 'test':
1892 sys.exit()
1893
1894 # for testing:
1895 logging.basicConfig(level = logging.DEBUG)
1896 from Gnumed.pycommon import gmI18N
1897 gmI18N.activate_locale()
1898 gmI18N.install_domain()
1899
1900 #-----------------------------------------------------------------------
1902
1903 tests = [
1904 [None, False],
1905
1906 ['', False],
1907 [' 0 ', True, 0],
1908
1909 [0, True, 0],
1910 [0.0, True, 0],
1911 [.0, True, 0],
1912 ['0', True, 0],
1913 ['0.0', True, 0],
1914 ['0,0', True, 0],
1915 ['00.0', True, 0],
1916 ['.0', True, 0],
1917 [',0', True, 0],
1918
1919 [0.1, True, decimal.Decimal('0.1')],
1920 [.01, True, decimal.Decimal('0.01')],
1921 ['0.1', True, decimal.Decimal('0.1')],
1922 ['0,1', True, decimal.Decimal('0.1')],
1923 ['00.1', True, decimal.Decimal('0.1')],
1924 ['.1', True, decimal.Decimal('0.1')],
1925 [',1', True, decimal.Decimal('0.1')],
1926
1927 [1, True, 1],
1928 [1.0, True, 1],
1929 ['1', True, 1],
1930 ['1.', True, 1],
1931 ['1,', True, 1],
1932 ['1.0', True, 1],
1933 ['1,0', True, 1],
1934 ['01.0', True, 1],
1935 ['01,0', True, 1],
1936 [' 01, ', True, 1],
1937
1938 [decimal.Decimal('1.1'), True, decimal.Decimal('1.1')]
1939 ]
1940 for test in tests:
1941 conversion_worked, result = input2decimal(initial = test[0])
1942
1943 expected2work = test[1]
1944
1945 if conversion_worked:
1946 if expected2work:
1947 if result == test[2]:
1948 continue
1949 else:
1950 print("ERROR (conversion result wrong): >%s<, expected >%s<, got >%s<" % (test[0], test[2], result))
1951 else:
1952 print("ERROR (conversion worked but was expected to fail): >%s<, got >%s<" % (test[0], result))
1953 else:
1954 if not expected2work:
1955 continue
1956 else:
1957 print("ERROR (conversion failed but was expected to work): >%s<, expected >%s<" % (test[0], test[2]))
1958 #-----------------------------------------------------------------------
1963 #-----------------------------------------------------------------------
1965
1966 val = None
1967 print(val, coalesce(val, 'is None', 'is not None'))
1968 val = 1
1969 print(val, coalesce(val, 'is None', 'is not None'))
1970 return
1971
1972 import datetime as dt
1973 print(coalesce(value2test = dt.datetime.now(), template4value = '-- %s --', function4value = ('strftime', '%Y-%m-%d')))
1974
1975 print('testing coalesce()')
1976 print("------------------")
1977 tests = [
1978 [None, 'something other than <None>', None, None, 'something other than <None>'],
1979 ['Captain', 'Mr.', '%s.'[:4], 'Mr.', 'Capt.'],
1980 ['value to test', 'test 3 failed', 'template with "%s" included', None, 'template with "value to test" included'],
1981 ['value to test', 'test 4 failed', 'template with value not included', None, 'template with value not included'],
1982 [None, 'initial value was None', 'template4value: %s', None, 'initial value was None'],
1983 [None, 'initial value was None', 'template4value: %%(abc)s', None, 'initial value was None']
1984 ]
1985 passed = True
1986 for test in tests:
1987 result = coalesce (
1988 value2test = test[0],
1989 return_instead = test[1],
1990 template4value = test[2],
1991 template4instead = test[3]
1992 )
1993 if result != test[4]:
1994 print("ERROR")
1995 print("coalesce: (%s, %s, %s, %s)" % (test[0], test[1], test[2], test[3]))
1996 print("expected:", test[4])
1997 print("received:", result)
1998 passed = False
1999
2000 if passed:
2001 print("passed")
2002 else:
2003 print("failed")
2004 return passed
2005 #-----------------------------------------------------------------------
2007 print('testing capitalize() ...')
2008 success = True
2009 pairs = [
2010 # [original, expected result, CAPS mode]
2011 ['Boot', 'Boot', CAPS_FIRST_ONLY],
2012 ['boot', 'Boot', CAPS_FIRST_ONLY],
2013 ['booT', 'Boot', CAPS_FIRST_ONLY],
2014 ['BoOt', 'Boot', CAPS_FIRST_ONLY],
2015 ['boots-Schau', 'Boots-Schau', CAPS_WORDS],
2016 ['boots-sChau', 'Boots-Schau', CAPS_WORDS],
2017 ['boot camp', 'Boot Camp', CAPS_WORDS],
2018 ['fahrner-Kampe', 'Fahrner-Kampe', CAPS_NAMES],
2019 ['häkkönen', 'Häkkönen', CAPS_NAMES],
2020 ['McBurney', 'McBurney', CAPS_NAMES],
2021 ['mcBurney', 'McBurney', CAPS_NAMES],
2022 ['blumberg', 'Blumberg', CAPS_NAMES],
2023 ['roVsing', 'RoVsing', CAPS_NAMES],
2024 ['Özdemir', 'Özdemir', CAPS_NAMES],
2025 ['özdemir', 'Özdemir', CAPS_NAMES],
2026 ]
2027 for pair in pairs:
2028 result = capitalize(pair[0], pair[2])
2029 if result != pair[1]:
2030 success = False
2031 print('ERROR (caps mode %s): "%s" -> "%s", expected "%s"' % (pair[2], pair[0], result, pair[1]))
2032
2033 if success:
2034 print("... SUCCESS")
2035
2036 return success
2037 #-----------------------------------------------------------------------
2039 print("testing import_module_from_directory()")
2040 path = sys.argv[1]
2041 name = sys.argv[2]
2042 try:
2043 mod = import_module_from_directory(module_path = path, module_name = name)
2044 except Exception:
2045 print("module import failed, see log")
2046 return False
2047
2048 print("module import succeeded", mod)
2049 print(dir(mod))
2050 return True
2051 #-----------------------------------------------------------------------
2055 #-----------------------------------------------------------------------
2057 print("testing gmPaths()")
2058 print("-----------------")
2059 paths = gmPaths(wx=None, app_name='gnumed')
2060 print("user config dir:", paths.user_config_dir)
2061 print("system config dir:", paths.system_config_dir)
2062 print("local base dir:", paths.local_base_dir)
2063 print("system app data dir:", paths.system_app_data_dir)
2064 print("working directory :", paths.working_dir)
2065 print("temp directory :", paths.tmp_dir)
2066 #-----------------------------------------------------------------------
2068 print("testing none_if()")
2069 print("-----------------")
2070 tests = [
2071 [None, None, None],
2072 ['a', 'a', None],
2073 ['a', 'b', 'a'],
2074 ['a', None, 'a'],
2075 [None, 'a', None],
2076 [1, 1, None],
2077 [1, 2, 1],
2078 [1, None, 1],
2079 [None, 1, None]
2080 ]
2081
2082 for test in tests:
2083 if none_if(value = test[0], none_equivalent = test[1]) != test[2]:
2084 print('ERROR: none_if(%s) returned [%s], expected [%s]' % (test[0], none_if(test[0], test[1]), test[2]))
2085
2086 return True
2087 #-----------------------------------------------------------------------
2089 tests = [
2090 [True, 'Yes', 'Yes', 'Yes'],
2091 [False, 'OK', 'not OK', 'not OK']
2092 ]
2093 for test in tests:
2094 if bool2str(test[0], test[1], test[2]) != test[3]:
2095 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]))
2096
2097 return True
2098 #-----------------------------------------------------------------------
2100
2101 print(bool2subst(True, 'True', 'False', 'is None'))
2102 print(bool2subst(False, 'True', 'False', 'is None'))
2103 print(bool2subst(None, 'True', 'False', 'is None'))
2104 #-----------------------------------------------------------------------
2106 print(get_unique_filename())
2107 print(get_unique_filename(prefix='test-'))
2108 print(get_unique_filename(suffix='tst'))
2109 print(get_unique_filename(prefix='test-', suffix='tst'))
2110 print(get_unique_filename(tmp_dir='/home/ncq/Archiv/'))
2111 #-----------------------------------------------------------------------
2113 print("testing size2str()")
2114 print("------------------")
2115 tests = [0, 1, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000, 1000000000000, 10000000000000]
2116 for test in tests:
2117 print(size2str(test))
2118 #-----------------------------------------------------------------------
2120
2121 test = """
2122 second line\n
2123 3rd starts with tab \n
2124 4th with a space \n
2125
2126 6th
2127
2128 """
2129 print(unwrap(text = test, max_length = 25))
2130 #-----------------------------------------------------------------------
2132 test = 'line 1\nline 2\nline 3'
2133
2134 print("wrap 5-6-7 initial 0, subsequent 0")
2135 print(wrap(test, 5))
2136 print()
2137 print(wrap(test, 6))
2138 print()
2139 print(wrap(test, 7))
2140 print("-------")
2141 input()
2142 print("wrap 5 initial 1-1-3, subsequent 1-3-1")
2143 print(wrap(test, 5, ' ', ' '))
2144 print()
2145 print(wrap(test, 5, ' ', ' '))
2146 print()
2147 print(wrap(test, 5, ' ', ' '))
2148 print("-------")
2149 input()
2150 print("wrap 6 initial 1-1-3, subsequent 1-3-1")
2151 print(wrap(test, 6, ' ', ' '))
2152 print()
2153 print(wrap(test, 6, ' ', ' '))
2154 print()
2155 print(wrap(test, 6, ' ', ' '))
2156 print("-------")
2157 input()
2158 print("wrap 7 initial 1-1-3, subsequent 1-3-1")
2159 print(wrap(test, 7, ' ', ' '))
2160 print()
2161 print(wrap(test, 7, ' ', ' '))
2162 print()
2163 print(wrap(test, 7, ' ', ' '))
2164 #-----------------------------------------------------------------------
2166 print('md5 %s: %s' % (sys.argv[2], file2md5(sys.argv[2])))
2167 print('chunked md5 %s: %s' % (sys.argv[2], file2chunked_md5(sys.argv[2])))
2168 #-----------------------------------------------------------------------
2170 print(u_link_symbol * 10)
2171 #-----------------------------------------------------------------------
2173 print(xml_escape_string('<'))
2174 print(xml_escape_string('>'))
2175 print(xml_escape_string('&'))
2176 #-----------------------------------------------------------------------
2178 tests = ['\\', '^', '~', '{', '}', '%', '&', '#', '$', '_', u_euro, 'abc\ndef\n\n1234']
2179 tests.append(' '.join(tests))
2180 for test in tests:
2181 print('%s:' % test, tex_escape_string(test))
2182
2183 #-----------------------------------------------------------------------
2185 tests = ['\\', '^', '~', '{', '}', '%', '&', '#', '$', '_', u_euro, 'abc\ndef\n\n1234']
2186 tests.append(' '.join(tests))
2187 tests.append('C:\Windows\Programme\System 32\lala.txt')
2188 tests.extend([
2189 'should be identical',
2190 'text *some text* text',
2191 """A List
2192 ======
2193
2194 1. 1
2195 2. 2
2196
2197 3. ist-list
2198 1. more
2199 2. noch was ü
2200 #. nummer x"""
2201 ])
2202 for test in tests:
2203 print('==================================================')
2204 print('raw:')
2205 print(test)
2206 print('---------')
2207 print('ReST 2 LaTeX:')
2208 latex = rst2latex_snippet(test)
2209 print(latex)
2210 if latex.strip() == test.strip():
2211 print('=> identical')
2212 print('---------')
2213 print('tex_escape_string:')
2214 print(tex_escape_string(test))
2215 input()
2216
2217 #-----------------------------------------------------------------------
2219 tests = [
2220 'one line, no embedded line breaks ',
2221 'one line\nwith embedded\nline\nbreaks\n '
2222 ]
2223 for test in tests:
2224 print('as list:')
2225 print(strip_trailing_empty_lines(text = test, eol='\n', return_list = True))
2226 print('as string:')
2227 print('>>>%s<<<' % strip_trailing_empty_lines(text = test, eol='\n', return_list = False))
2228 tests = [
2229 ['list', 'without', 'empty', 'trailing', 'lines'],
2230 ['list', 'with', 'empty', 'trailing', 'lines', '', ' ', '']
2231 ]
2232 for test in tests:
2233 print('as list:')
2234 print(strip_trailing_empty_lines(lines = test, eol = '\n', return_list = True))
2235 print('as string:')
2236 print(strip_trailing_empty_lines(lines = test, eol = '\n', return_list = False))
2237 #-----------------------------------------------------------------------
2239 tests = [
2240 r'abc.exe',
2241 r'\abc.exe',
2242 r'c:\abc.exe',
2243 r'c:\d\abc.exe',
2244 r'/home/ncq/tmp.txt',
2245 r'~/tmp.txt',
2246 r'./tmp.txt',
2247 r'./.././tmp.txt',
2248 r'tmp.txt'
2249 ]
2250 for t in tests:
2251 print("[%s] -> [%s]" % (t, fname_stem(t)))
2252 #-----------------------------------------------------------------------
2254 print(sys.argv[2], 'empty:', dir_is_empty(sys.argv[2]))
2255
2256 #-----------------------------------------------------------------------
2258 d1 = {}
2259 d2 = {}
2260 d1[1] = 1
2261 d1[2] = 2
2262 d1[3] = 3
2263 # 4
2264 d1[5] = 5
2265
2266 d2[1] = 1
2267 d2[2] = None
2268 # 3
2269 d2[4] = 4
2270
2271 #compare_dict_likes(d1, d2)
2272
2273 d1 = {1: 1, 2: 2}
2274 d2 = {1: 1, 2: 2}
2275
2276 #compare_dict_likes(d1, d2, 'same1', 'same2')
2277 print(format_dict_like(d1, tabular = False))
2278 print(format_dict_like(d1, tabular = True))
2279 #print(format_dict_like(d2))
2280
2281 #-----------------------------------------------------------------------
2283 d1 = {}
2284 d2 = {}
2285 d1[1] = 1
2286 d1[2] = 2
2287 d1[3] = 3
2288 # 4
2289 d1[5] = 5
2290
2291 d2[1] = 1
2292 d2[2] = None
2293 # 3
2294 d2[4] = 4
2295
2296 print('\n'.join(format_dict_likes_comparison(d1, d2, 'd1', 'd2')))
2297
2298 d1 = {1: 1, 2: 2}
2299 d2 = {1: 1, 2: 2}
2300
2301 print('\n'.join(format_dict_likes_comparison(d1, d2, 'd1', 'd2')))
2302
2303 #-----------------------------------------------------------------------
2305 rmdir('cx:\windows\system3__2xxxxxxxxxxxxx')
2306
2307 #-----------------------------------------------------------------------
2309 #print(rm_dir_content('cx:\windows\system3__2xxxxxxxxxxxxx'))
2310 print(rm_dir_content('/tmp/user/1000/tmp'))
2311
2312 #-----------------------------------------------------------------------
2314 tests = [
2315 ('', '', ''),
2316 ('a', 'a', ''),
2317 ('GMd: a window title', _GM_TITLE_PREFIX + ':', 'a window title'),
2318 ('\.br\MICROCYTES+1\.br\SPHEROCYTES present\.br\POLYCHROMASIAmoderate\.br\\', '\.br\\', 'MICROCYTES+1\.br\SPHEROCYTES present\.br\POLYCHROMASIAmoderate\.br\\')
2319 ]
2320 for test in tests:
2321 text, prefix, expect = test
2322 result = strip_prefix(text, prefix, remove_whitespace = True)
2323 if result == expect:
2324 continue
2325 print('test failed:', test)
2326 print('result:', result)
2327
2328 #-----------------------------------------------------------------------
2330 tst = [
2331 ('123', 1),
2332 ('123', 2),
2333 ('123', 3),
2334 ('123', 4),
2335 ('', 1),
2336 ('1', 1),
2337 ('12', 1),
2338 ('', 2),
2339 ('1', 2),
2340 ('12', 2),
2341 ('123', 2)
2342 ]
2343 for txt, lng in tst:
2344 print('max', lng, 'of', txt, '=', shorten_text(txt, lng))
2345 #-----------------------------------------------------------------------
2347 tests = [
2348 '/tmp/test.txt',
2349 '/tmp/ test.txt',
2350 '/tmp/ tes\\t.txt',
2351 'test'
2352 ]
2353 for test in tests:
2354 print (test, fname_sanitize(test))
2355
2356 #-----------------------------------------------------------------------
2359
2360 #-----------------------------------------------------------------------
2362 parts = enumerate_removable_partitions()
2363 for part_name in parts:
2364 part = parts[part_name]
2365 print(part['device'])
2366 print(part['partition'])
2367 if part['is_mounted']:
2368 print('%s@%s: %s on %s by %s @ %s (FS=%s: %s free of %s total)' % (
2369 part['type'],
2370 part['bus'],
2371 part['fs_label'],
2372 part['model'],
2373 part['vendor'],
2374 part['mountpoint'],
2375 part['fs_type'],
2376 part['bytes_free'],
2377 part['size_in_bytes']
2378 ))
2379 else:
2380 print('%s@%s: %s on %s by %s (not mounted)' % (
2381 part['type'],
2382 part['bus'],
2383 part['fs_label'],
2384 part['model'],
2385 part['vendor']
2386 ))
2387
2388 #-----------------------------------------------------------------------
2390 for writer in enumerate_optical_writers():
2391 print('%s@%s: %s @ %s' % (
2392 writer['type'],
2393 writer['bus'],
2394 writer['model'],
2395 writer['device']
2396 ))
2397
2398 #-----------------------------------------------------------------------
2402
2403 #-----------------------------------------------------------------------
2405 print(mk_sandbox_dir(base_dir = '/tmp/abcd/efg/h'))
2406
2407 #-----------------------------------------------------------------------
2409 dicts = [
2410 {'pkey': 1, 'value': 'a1'},
2411 {'pkey': 2, 'value': 'b2'},
2412 {'pkey': 3, 'value': 'c3'},
2413 {'pkey': 4, 'value': 'd4'},
2414 {'pkey': 5, 'value': 'd4'},
2415 {'pkey': 5, 'value': 'c5'},
2416 ]
2417 with open('x.txt', 'w', encoding = 'utf8') as f:
2418 f.write(dicts2table(dicts, left_margin=2, eol='\n', keys2ignore=None, show_only_changes=True, headers = ['d1', 'd2', 'd3', 'd4', 'd5', 'd6']))
2419 #print(dicts2table(dicts, left_margin=2, eol='\n', keys2ignore=None, show_only_changes=True, headers = ['d1', 'd2', 'd3', 'd4', 'd5', 'd6']))
2420
2421 #-----------------------------------------------------------------------
2425
2426 #-----------------------------------------------------------------------
2427 #test_coalesce()
2428 #test_capitalize()
2429 #test_import_module()
2430 #test_mkdir()
2431 #test_gmPaths()
2432 #test_none_if()
2433 #test_bool2str()
2434 #test_bool2subst()
2435 #test_get_unique_filename()
2436 #test_size2str()
2437 #test_wrap()
2438 #test_input2decimal()
2439 #test_input2int()
2440 #test_unwrap()
2441 #test_md5()
2442 #test_unicode()
2443 #test_xml_escape()
2444 #test_strip_trailing_empty_lines()
2445 #test_fname_stem()
2446 #test_tex_escape()
2447 #test_rst2latex_snippet()
2448 #test_dir_is_empty()
2449 #test_compare_dicts()
2450 #test_rm_dir()
2451 #test_rm_dir_content()
2452 #test_strip_prefix()
2453 #test_shorten_text()
2454 #test_format_compare_dicts()
2455 #test_fname_sanitize()
2456 #test_create_qrcode()
2457 #test_enumerate_removable_partitions()
2458 #test_enumerate_optical_writers()
2459 #test_copy_tree_content()
2460 #test_mk_sandbox_dir()
2461 #test_make_table_from_dicts()
2462 test_decorate_window_title()
2463
2464 #===========================================================================
2465
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Sun Nov 10 02:55:34 2019 | http://epydoc.sourceforge.net |