| 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 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' # Yen kanji
131 u_replacement_character = '\ufffd'
132 u_link_symbol = '\u1f517'
133
134 _kB = 1024
135 _MB = 1024 * _kB
136 _GB = 1024 * _MB
137 _TB = 1024 * _GB
138 _PB = 1024 * _TB
139
140 #===========================================================================
142
143 print(".========================================================")
144 print("| Unhandled exception caught !")
145 print("| Type :", t)
146 print("| Value:", v)
147 print("`========================================================")
148 _log.critical('unhandled exception caught', exc_info = (t,v,tb))
149 sys.__excepthook__(t,v,tb)
150
151 #===========================================================================
152 # path level operations
153 #---------------------------------------------------------------------------
155 """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 # does not WORK !
167 #os.chmod(directory, mode, follow_symlinks = (os.chmod in os.supports_follow_symlinks)) # can't do better
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 #---------------------------------------------------------------------------
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 # attempt file/link removal and ignore (but log) errors
216 full_item = os.path.join(directory, item)
217 try:
218 os.remove(full_item)
219 except OSError: # as per the docs, this is a directory
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) # (invoking user only)
237 if prefix is None:
238 prefix = 'sndbx-'
239 return tempfile.mkdtemp(prefix = prefix, suffix = '', dir = base_dir)
240
241 #---------------------------------------------------------------------------
244
245 #---------------------------------------------------------------------------
247 # /home/user/dir/ -> dir
248 # /home/user/dir -> dir
249 return os.path.basename(os.path.normpath(directory)) # normpath removes trailing slashes if any
250
251 #---------------------------------------------------------------------------
253 try:
254 return len(os.listdir(directory)) == 0
255 except OSError as exc:
256 if exc.errno == 2: # no such file
257 return None
258 raise
259
260 #---------------------------------------------------------------------------
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 """
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 # public API
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 # the user home, doesn't work in Wine so work around that
335 self.__home_dir = None
336
337 # where the main script (the "binary") is installed
338 if getattr(sys, 'frozen', False):
339 _log.info('frozen app, installed into temporary path')
340 # this would find the path of *THIS* file
341 #self.local_base_dir = os.path.dirname(__file__)
342 # while this is documented on the web, the ${_MEIPASS2} does not exist
343 #self.local_base_dir = os.environ.get('_MEIPASS2')
344 # this is what Martin Zibricky <mzibr.public@gmail.com> told us to use
345 # when asking about this on pyinstaller@googlegroups.com
346 #self.local_base_dir = sys._MEIPASS
347 # however, we are --onedir, so we should look at sys.executable
348 # as per the pyinstaller manual
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 # the current working dir at the OS
354 self.working_dir = os.path.abspath(os.curdir)
355
356 # user-specific config dir, usually below the home dir
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 # system-wide config dir, usually below /etc/ under UN*X
361 try:
362 self.system_config_dir = os.path.join('/etc', app_name)
363 except ValueError:
364 #self.system_config_dir = self.local_base_dir
365 self.system_config_dir = self.user_config_dir
366
367 # system-wide application data dir
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 # temporary directory
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 # $TMP/gnumed-$USER/
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 # $TMP/gnumed-$USER/g-$UNIQUE/
388 self.tmp_dir = tempfile.mkdtemp(prefix = 'g-')
389 _log.info('final (app instance level) temp dir: %s', tempfile.gettempdir())
390
391 # BYTEA cache dir
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 # retry with wxPython
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 # user-specific config dir, usually below the home dir
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 # system-wide config dir, usually below /etc/ under UN*X
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 # leave it at what it was from direct detection
423 pass
424
425 # system-wide application data dir
426 # Robin attests that the following doesn't always
427 # give sane values on Windows, so IFDEF it
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 # properties
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
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
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
492
493 system_app_data_dir = property(_get_system_app_data_dir, _set_system_app_data_dir)
494 #--------------------------------------
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
540
541 tmp_dir = property(_get_tmp_dir, _set_tmp_dir)
542
543 #===========================================================================
544 # file related tools
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 #---------------------------------------------------------------------------
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 #---------------------------------------------------------------------------
586 if not os.path.lexists(filename):
587 return True
588
589 # attempt file remove and ignore (but log) errors
590 try:
591 os.remove(filename)
592 return True
593
594 except Exception:
595 if log_error:
596 _log.exception('cannot os.remove(%s)', filename)
597
598 if force:
599 tmp_name = get_unique_filename(tmp_dir = fname_dir(filename))
600 _log.debug('attempting os.replace(%s -> %s)', filename, tmp_name)
601 try:
602 os.replace(filename, tmp_name)
603 return True
604
605 except Exception:
606 if log_error:
607 _log.exception('cannot os.remove(%s)', filename)
608
609 return False
610
611 #---------------------------------------------------------------------------
613 blocksize = 2**10 * 128 # 128k, since md5 uses 128 byte blocks
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
672
673 #def utf_8_encoder(unicode_csv_data):
674 # for line in unicode_csv_data:
675 # yield line.encode('utf-8')
676
678
679 # csv.py doesn't do Unicode; encode temporarily as UTF-8:
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 # decode ENCODING back to Unicode, cell by cell:
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 #yield [str(cell, 'utf-8') for cell in row]
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 # remove everything not in group []
721 name_part = regex.sub (
722 '[^.\w\s[\]()%§+-]',
723 '',
724 name_part,
725 flags = regex.UNICODE
726 ).strip()
727 # translate whitespace to underscore
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 #---------------------------------------------------------------------------
762
763 #---------------------------------------------------------------------------
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 # make sure file gets deleted as soon as
794 # .close()d so we can "safely" open it again
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 #windows_create_symlink = ctypes.windll.kernel32.CreateSymbolicLinkW
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 #---------------------------------------------------------------------------
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 #except OSError:
856 # unpriviledged on Windows
857 return True
858
859 #===========================================================================
860 -def import_module_from_directory(module_path=None, module_name=None, always_remove_path=False):
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 # text related tools
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 #---------------------------------------------------------------------------
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 #---------------------------------------------------------------------------
921 return bool2subst (
922 boolean = bool(boolean),
923 true_return = true_str,
924 false_return = false_str
925 )
926
927 #---------------------------------------------------------------------------
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 # at this point, value2test was not equivalent to None
976
977 # 1) explicit value to return supplied ?
978 if value2return is not None:
979 return value2return
980
981 value2return = value2test
982 # 2) function supplied to be applied to the value ?
983 if function4value is not None:
984 funcname, args = function4value
985 func = getattr(value2test, funcname)
986 value2return = func(args)
987
988 # 3) template supplied to be applied to the value ?
989 if template4value is None:
990 return value2return
991
992 try:
993 return template4value % value2return
994 except TypeError:
995 # except (TypeError, ValueError):
996 # this should go, actually, only needed because "old" calls
997 # to coalesce will still abuse template4value as explicit value2return,
998 # relying on the replacement to above to fail
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']: # FIXME: this needs to expand, configurable ?
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 # if len(text) == 1:
1041 # return text[0].upper()
1042 return text[0].upper() + text[1:].lower()
1043
1044 if mode == CAPS_WORDS:
1045 #return regex.sub(ur'(\w)(\w+)', lambda x: x.group(1).upper() + x.group(2).lower(), text)
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 #return regex.sub(r'\w+', __cap_name, text)
1050 return capitalize(text=text, mode=CAPS_FIRST) # until fixed
1051
1052 print("ERROR: invalid capitalization mode: [%s], leaving input as is" % mode)
1053 return text
1054
1055 #---------------------------------------------------------------------------
1057
1058 if isinstance(initial, decimal.Decimal):
1059 return True, initial
1060
1061 val = initial
1062
1063 # float ? -> to string first
1064 if type(val) == type(float(1.4)):
1065 val = str(val)
1066
1067 # string ? -> "," to "."
1068 if isinstance(val, str):
1069 val = val.replace(',', '.', 1)
1070 val = val.strip()
1071
1072 try:
1073 d = decimal.Decimal(val)
1074 return True, d
1075 except (TypeError, decimal.InvalidOperation):
1076 return False, val
1077
1078 #---------------------------------------------------------------------------
1080
1081 val = initial
1082
1083 # string ? -> "," to "."
1084 if isinstance(val, str):
1085 val = val.replace(',', '.', 1)
1086 val = val.strip()
1087
1088 try:
1089 int_val = int(val)
1090 except (TypeError, ValueError):
1091 _log.exception('int(%s) failed', val)
1092 return False, initial
1093
1094 if minval is not None:
1095 if int_val < minval:
1096 _log.debug('%s < min (%s)', val, minval)
1097 return False, initial
1098 if maxval is not None:
1099 if int_val > maxval:
1100 _log.debug('%s > max (%s)', val, maxval)
1101 return False, initial
1102
1103 return True, int_val
1104
1105 #---------------------------------------------------------------------------
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 #---------------------------------------------------------------------------
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 #---------------------------------------------------------------------------
1166 return strip_trailing_empty_lines (
1167 lines = strip_leading_empty_lines(lines = lines, text = text, eol = eol, return_list = True),
1168 text = None,
1169 eol = eol,
1170 return_list = return_list
1171 )
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 #---------------------------------------------------------------------------
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 #---------------------------------------------------------------------------
1250
1251 if len(text) <= max_length:
1252 return text
1253
1254 return text[:max_length-1] + u_ellipsis
1255
1256 #---------------------------------------------------------------------------
1257 -def shorten_words_in_line(text=None, max_length=None, min_word_length=None, ignore_numbers=True, ellipsis=u_ellipsis):
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 #---------------------------------------------------------------------------
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') # requires \usepackage{textcomp} in LaTeX source
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 # this should NOT be replaced for Xe(La)Tex
1318 text = text.replace(u_euro, '\\EUR') # requires \usepackage{textcomp} in LaTeX source
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 #destination_path = '/path/to/LaTeX-template/for/calculating/relative/links/template.tex',
1338 settings_overrides = {
1339 'input_encoding': 'unicode' # un-encoded unicode
1340 },
1341 enable_exit_status = True # how to use ?
1342 )
1343 return parts['body']
1344
1345 #---------------------------------------------------------------------------
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 #destination_path = '/path/to/LaTeX-template/for/calculating/relative/links/template.tex',
1360 settings_overrides = {
1361 'input_encoding': 'unicode' # un-encoded unicode
1362 },
1363 enable_exit_status = True # how to use ?
1364 )
1365 return parts['body']
1366
1367 #---------------------------------------------------------------------------
1369 # a web search did not reveal anything else for Xe(La)Tex
1370 # as opposed to LaTeX, except true unicode chars
1371 return tex_escape_string(text = text, replace_known_unicode = False)
1372
1373 #---------------------------------------------------------------------------
1374 __html_escape_table = {
1375 "&": "&",
1376 '"': """,
1377 "'": "'",
1378 ">": ">",
1379 "<": "<",
1380 }
1381
1383 text = ''.join(__html_escape_table.get(char, char) for char in text)
1384 if replace_eol:
1385 if keep_visual_eol:
1386 text = text.replace('\n', '<br>\n')
1387 else:
1388 text = text.replace('\n', '<br>')
1389 return text
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 #---------------------------------------------------------------------------
1447 -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):
1448
1449 _log.info('comparing dict-likes: %s[%s] vs %s[%s]', coalesce(title_left, '', '"%s" '), type(d1), coalesce(title_right, '', '"%s" '), type(d2))
1450 append_type = False
1451 if None not in [title_left, title_right]:
1452 append_type = True
1453 type_left = type(d1)
1454 type_right = type(d2)
1455 if title_left is None:
1456 title_left = '%s' % type_left
1457 if title_right is None:
1458 title_right = '%s' % type_right
1459
1460 try: d1 = dict(d1)
1461 except TypeError: pass
1462 try: d2 = dict(d2)
1463 except TypeError: pass
1464 keys_d1 = d1.keys()
1465 keys_d2 = d2.keys()
1466 data = {}
1467 for key in keys_d1:
1468 data[key] = [d1[key], ' ']
1469 if key in d2:
1470 data[key][1] = d2[key]
1471 for key in keys_d2:
1472 if key in keys_d1:
1473 continue
1474 data[key] = [' ', d2[key]]
1475 max1 = max([ len('%s' % k) for k in keys_d1 ])
1476 max2 = max([ len('%s' % k) for k in keys_d2 ])
1477 max_len = max(max1, max2, len(_('<type>')))
1478 max_key_len_str = '%' + '%s.%s' % (max_len, max_len) + 's'
1479 max1 = max([ len('%s' % d1[k]) for k in keys_d1 ])
1480 max2 = max([ len('%s' % d2[k]) for k in keys_d2 ])
1481 max_data_len = min(max(max1, max2), 100)
1482 max_data_len_str = '%' + '%s.%s' % (max_data_len, max_data_len) + 's'
1483 diff_indicator_len_str = '%' + '%s.%s' % (len(difference_indicator), len(difference_indicator)) + 's'
1484 line_template = (' ' * left_margin) + diff_indicator_len_str + max_key_len_str + key_delim + max_data_len_str + data_delim + '%s'
1485
1486 lines = []
1487 # debugging:
1488 #lines.append(u' (40 regular spaces)')
1489 #lines.append((u' ' * 40) + u"(u' ' * 40)")
1490 #lines.append((u'%40.40s' % u'') + u"(u'%40.40s' % u'')")
1491 #lines.append((u'%40.40s' % u' ') + u"(u'%40.40s' % u' ')")
1492 #lines.append((u'%40.40s' % u'.') + u"(u'%40.40s' % u'.')")
1493 #lines.append(line_template)
1494 lines.append(line_template % ('', '', title_left, title_right))
1495 if append_type:
1496 lines.append(line_template % ('', _('<type>'), type_left, type_right))
1497
1498 if ignore_diff_in_keys is None:
1499 ignore_diff_in_keys = []
1500
1501 for key in keys_d1:
1502 append_type = False
1503 txt_left_col = '%s' % d1[key]
1504 try:
1505 txt_right_col = '%s' % d2[key]
1506 if type(d1[key]) != type(d2[key]):
1507 append_type = True
1508 except KeyError:
1509 txt_right_col = missing_string
1510 lines.append(line_template % (
1511 bool2subst (
1512 ((txt_left_col == txt_right_col) or (key in ignore_diff_in_keys)),
1513 '',
1514 difference_indicator
1515 ),
1516 key,
1517 shorten_text(txt_left_col, max_data_len),
1518 shorten_text(txt_right_col, max_data_len)
1519 ))
1520 if append_type:
1521 lines.append(line_template % (
1522 '',
1523 _('<type>'),
1524 shorten_text('%s' % type(d1[key]), max_data_len),
1525 shorten_text('%s' % type(d2[key]), max_data_len)
1526 ))
1527
1528 for key in keys_d2:
1529 if key in keys_d1:
1530 continue
1531 lines.append(line_template % (
1532 bool2subst((key in ignore_diff_in_keys), '', difference_indicator),
1533 key,
1534 shorten_text(missing_string, max_data_len),
1535 shorten_text('%s' % d2[key], max_data_len)
1536 ))
1537
1538 return lines
1539
1540 #---------------------------------------------------------------------------
1541 -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):
1542 if values2ignore is None:
1543 values2ignore = []
1544 if template is not None:
1545 # all keys in template better exist in d
1546 try:
1547 return template % d
1548 except KeyError:
1549 # or else
1550 _log.exception('template contains %%()s key(s) which do not exist in data dict')
1551 # try to extend dict <d> to contain all required keys,
1552 # for that to work <relevant_keys> better list all
1553 # keys used in <template>
1554 if relevant_keys is not None:
1555 for key in relevant_keys:
1556 try:
1557 d[key]
1558 except KeyError:
1559 d[key] = missing_key_template % {'key': key}
1560 return template % d
1561
1562 if relevant_keys is None:
1563 relevant_keys = list(d.keys())
1564 lines = []
1565 if value_delimiters is None:
1566 delim_left = ''
1567 delim_right = ''
1568 else:
1569 delim_left, delim_right = value_delimiters
1570 if tabular:
1571 max_len = max([ len('%s' % k) for k in relevant_keys ])
1572 max_len_str = '%s.%s' % (max_len, max_len)
1573 line_template = (' ' * left_margin) + '%' + max_len_str + ('s: %s%%s%s' % (delim_left, delim_right))
1574 else:
1575 line_template = (' ' * left_margin) + '%%s: %s%%s%s' % (delim_left, delim_right)
1576 for key in relevant_keys:
1577 try:
1578 val = d[key]
1579 except KeyError:
1580 continue
1581 if val not in values2ignore:
1582 lines.append(line_template % (key, val))
1583 if eol is None:
1584 return lines
1585 return eol.join(lines)
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): #, relevant_keys=None, template=None
1589
1590 keys2show = []
1591 dict_max_size = {}
1592 max_row_label_size = 0
1593 if keys2ignore is None:
1594 keys2ignore = []
1595
1596 # extract keys from all dicts
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 # pivot data into dict of lists per line
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 # format data into table
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 # add header line if any
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 # not mounted
1719 removable_partitions.update(partitions_on_removable_device)
1720 return removable_partitions
1721
1722 # debugging:
1723 #ctxt = pyudev.Context()
1724 #for dev in ctxt.list_devices(subsystem='block', DEVTYPE='disk'):# if dev.attributes.get('removable') == b'1':
1725 # for a in dev.attributes.available_attributes:
1726 # print(a, dev.attributes.get(a))
1727 # for key, value in dev.items():
1728 # print('{key}={value}'.format(key=key, value=value))
1729 # print('---------------------------')
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 #---------------------------------------------------------------------------
1753 """Obtains entry from standard input.
1754
1755 prompt: Prompt text to display in standard output
1756 default: Default value (for user to press enter only)
1757 CTRL-C: aborts and returns None
1758 """
1759 if prompt is None:
1760 msg = '(CTRL-C aborts)'
1761 else:
1762 msg = '%s (CTRL-C aborts)' % prompt
1763
1764 if default is None:
1765 msg = msg + ': '
1766 else:
1767 msg = '%s [%s]: ' % (msg, default)
1768
1769 try:
1770 usr_input = input(msg)
1771 except KeyboardInterrupt:
1772 return None
1773
1774 if usr_input == '':
1775 return default
1776
1777 return usr_input
1778
1779 #===========================================================================
1780 # image handling tools
1781 #---------------------------------------------------------------------------
1782 # builtin (ugly but tried and true) fallback icon
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) #_PNG
1821 except AttributeError:
1822 _log.exception("this platform doesn't support wx.Icon().LoadFile()")
1823
1824 return icon
1825
1826 #---------------------------------------------------------------------------
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 # main
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 # for testing:
1868 logging.basicConfig(level = logging.DEBUG)
1869 from Gnumed.pycommon import gmI18N
1870 gmI18N.activate_locale()
1871 gmI18N.install_domain()
1872
1873 #-----------------------------------------------------------------------
1875
1876 tests = [
1877 [None, False],
1878
1879 ['', False],
1880 [' 0 ', True, 0],
1881
1882 [0, True, 0],
1883 [0.0, True, 0],
1884 [.0, True, 0],
1885 ['0', True, 0],
1886 ['0.0', True, 0],
1887 ['0,0', True, 0],
1888 ['00.0', True, 0],
1889 ['.0', True, 0],
1890 [',0', True, 0],
1891
1892 [0.1, True, decimal.Decimal('0.1')],
1893 [.01, True, decimal.Decimal('0.01')],
1894 ['0.1', True, decimal.Decimal('0.1')],
1895 ['0,1', True, decimal.Decimal('0.1')],
1896 ['00.1', True, decimal.Decimal('0.1')],
1897 ['.1', True, decimal.Decimal('0.1')],
1898 [',1', True, decimal.Decimal('0.1')],
1899
1900 [1, True, 1],
1901 [1.0, True, 1],
1902 ['1', True, 1],
1903 ['1.', True, 1],
1904 ['1,', True, 1],
1905 ['1.0', True, 1],
1906 ['1,0', True, 1],
1907 ['01.0', True, 1],
1908 ['01,0', True, 1],
1909 [' 01, ', True, 1],
1910
1911 [decimal.Decimal('1.1'), True, decimal.Decimal('1.1')]
1912 ]
1913 for test in tests:
1914 conversion_worked, result = input2decimal(initial = test[0])
1915
1916 expected2work = test[1]
1917
1918 if conversion_worked:
1919 if expected2work:
1920 if result == test[2]:
1921 continue
1922 else:
1923 print("ERROR (conversion result wrong): >%s<, expected >%s<, got >%s<" % (test[0], test[2], result))
1924 else:
1925 print("ERROR (conversion worked but was expected to fail): >%s<, got >%s<" % (test[0], result))
1926 else:
1927 if not expected2work:
1928 continue
1929 else:
1930 print("ERROR (conversion failed but was expected to work): >%s<, expected >%s<" % (test[0], test[2]))
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 # [original, expected result, CAPS mode]
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 #-----------------------------------------------------------------------
2028 #-----------------------------------------------------------------------
2030 print("testing gmPaths()")
2031 print("-----------------")
2032 paths = gmPaths(wx=None, app_name='gnumed')
2033 print("user config dir:", paths.user_config_dir)
2034 print("system config dir:", paths.system_config_dir)
2035 print("local base dir:", paths.local_base_dir)
2036 print("system app data dir:", paths.system_app_data_dir)
2037 print("working directory :", paths.working_dir)
2038 print("temp directory :", paths.tmp_dir)
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 #-----------------------------------------------------------------------
2079 print(get_unique_filename())
2080 print(get_unique_filename(prefix='test-'))
2081 print(get_unique_filename(suffix='tst'))
2082 print(get_unique_filename(prefix='test-', suffix='tst'))
2083 print(get_unique_filename(tmp_dir='/home/ncq/Archiv/'))
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 #-----------------------------------------------------------------------
2143 print(u_link_symbol * 10)
2144 #-----------------------------------------------------------------------
2146 print(xml_escape_string('<'))
2147 print(xml_escape_string('>'))
2148 print(xml_escape_string('&'))
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 # 4
2237 d1[5] = 5
2238
2239 d2[1] = 1
2240 d2[2] = None
2241 # 3
2242 d2[4] = 4
2243
2244 #compare_dict_likes(d1, d2)
2245
2246 d1 = {1: 1, 2: 2}
2247 d2 = {1: 1, 2: 2}
2248
2249 #compare_dict_likes(d1, d2, 'same1', 'same2')
2250 print(format_dict_like(d1, tabular = False))
2251 print(format_dict_like(d1, tabular = True))
2252 #print(format_dict_like(d2))
2253
2254 #-----------------------------------------------------------------------
2256 d1 = {}
2257 d2 = {}
2258 d1[1] = 1
2259 d1[2] = 2
2260 d1[3] = 3
2261 # 4
2262 d1[5] = 5
2263
2264 d2[1] = 1
2265 d2[2] = None
2266 # 3
2267 d2[4] = 4
2268
2269 print('\n'.join(format_dict_likes_comparison(d1, d2, 'd1', 'd2')))
2270
2271 d1 = {1: 1, 2: 2}
2272 d2 = {1: 1, 2: 2}
2273
2274 print('\n'.join(format_dict_likes_comparison(d1, d2, 'd1', 'd2')))
2275
2276 #-----------------------------------------------------------------------
2278 rmdir('cx:\windows\system3__2xxxxxxxxxxxxx')
2279
2280 #-----------------------------------------------------------------------
2282 #print(rm_dir_content('cx:\windows\system3__2xxxxxxxxxxxxx'))
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 #-----------------------------------------------------------------------
2373
2374 #-----------------------------------------------------------------------
2376 print(mk_sandbox_dir(base_dir = '/tmp/abcd/efg/h'))
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 #print(dicts2table(dicts, left_margin=2, eol='\n', keys2ignore=None, show_only_changes=True, headers = ['d1', 'd2', 'd3', 'd4', 'd5', 'd6']))
2391
2392 #-----------------------------------------------------------------------
2393 test_coalesce()
2394 #test_capitalize()
2395 #test_import_module()
2396 #test_mkdir()
2397 #test_gmPaths()
2398 #test_none_if()
2399 #test_bool2str()
2400 #test_bool2subst()
2401 #test_get_unique_filename()
2402 #test_size2str()
2403 #test_wrap()
2404 #test_input2decimal()
2405 #test_input2int()
2406 #test_unwrap()
2407 #test_md5()
2408 #test_unicode()
2409 #test_xml_escape()
2410 #test_strip_trailing_empty_lines()
2411 #test_fname_stem()
2412 #test_tex_escape()
2413 #test_rst2latex_snippet()
2414 #test_dir_is_empty()
2415 #test_compare_dicts()
2416 #test_rm_dir()
2417 #test_rm_dir_content()
2418 #test_strip_prefix()
2419 #test_shorten_text()
2420 #test_format_compare_dicts()
2421 #test_fname_sanitize()
2422 #test_create_qrcode()
2423 #test_enumerate_removable_partitions()
2424 #test_enumerate_optical_writers()
2425 #test_copy_tree_content()
2426 #test_mk_sandbox_dir()
2427 #test_make_table_from_dicts()
2428
2429 #===========================================================================
2430
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Sun Jul 28 01:55:29 2019 | http://epydoc.sourceforge.net |