54 from collections
import OrderedDict
57 from natsort
import natsorted
59 except( ImportError, ValueError ):
61 def natsorted(*args, **kwargs):
62 raise RuntimeError(
"The library natsort is required to run this function.\nYou can install it with `pip install natsort`" )
65 import diff_match_patch
67 except( ImportError, ValueError ):
69 diff_match_patch =
None 74 if sys.version_info[0] < 3:
76 Event = threading._Event
79 Event = threading.Event
84 super( SleepEvent, self ).__init__()
88 If blockOnSleepCall() was called before, this will sleep the current thread for ever if 89 not arguments are passed. Otherwise, it accepts a positive floating point number for 90 seconds in which this thread will sleep. 92 If disableSleepCall() is called before the timeout has passed, it will immediately get 93 out of sleep and this thread will wake up. 97 def blockOnSleepCall(self):
100 def disableSleepCall(self):
106 def textwrap_indent(text, prefix, predicate=None):
107 """Adds 'prefix' to the beginning of selected lines in 'text'. 108 If 'predicate' is provided, 'prefix' will only be added to the lines 109 where 'predicate(line)' is True. If 'predicate' is not provided, 110 it will default to adding 'prefix' to all non-empty lines that do not 111 consist solely of whitespace characters. 113 if predicate
is None:
117 def prefixed_lines():
118 for line
in text.splitlines(
True):
119 yield (prefix + line
if predicate(line)
else line)
120 return u"".join(prefixed_lines())
123 textwrap_indent = textwrap.indent
130 _g_maximum_lines = 40000
131 _g_char_limit = 65535
136 _g_maximum_lines = 666666
137 _g_char_limit = 1114111
142 """Convert a diff array into a pretty Text report. 144 diffs: Array of diff tuples. 149 last_op_type = self.DIFF_EQUAL
152 cut_next_new_line = [
False]
155 operations = (self.DIFF_INSERT, self.DIFF_DELETE)
166 new = textwrap_indent(
"%s" % new, sign,
lambda line:
True )
169 if len(results_diff) > 0:
174 if op == self.DIFF_INSERT
and next_text
and new[-1] ==
'\n' and next_text[0] ==
'\n':
175 cut_next_new_line[0] =
True;
178 if len(text) > 1: new = new +
'%s\n' % sign
180 elif next_op
not in operations
and next_text
and next_text[0] !=
'\n':
186 for index
in range(len(diffs)):
187 op, text = diffs[index]
188 if index < len(diffs) - 1:
189 next_op, next_text = diffs[index+1]
191 next_op, next_text = (0,
"")
193 if op == self.DIFF_INSERT:
194 results_diff.append( parse(
"+ " ) )
196 elif op == self.DIFF_DELETE:
197 results_diff.append( parse(
"- " ) )
199 elif op == self.DIFF_EQUAL:
202 text = textwrap_indent(text,
" ")
204 if cut_next_new_line[0]:
205 cut_next_new_line[0] =
False 208 results_diff.append(text)
214 return "".join(results_diff)
218 Split two texts into an array of strings. Reduce the texts to a string 219 of hashes where each Unicode character represents one line. 221 95% of this function code is copied from `diff_linesToChars` on: 222 https://github.com/google/diff-match-patch/blob/895a9512bbcee0ac5a8ffcee36062c8a79f5dcda/python3/diff_match_patch.py#L381 224 Copyright 2018 The diff-match-patch Authors. 225 https://github.com/google/diff-match-patch 226 Licensed under the Apache License, Version 2.0 (the "License"); 227 you may not use this file except in compliance with the License. 228 You may obtain a copy of the License at 229 http://www.apache.org/licenses/LICENSE-2.0 233 text2: Second string. 234 delimiter: a re.compile() expression for the word delimiter type 237 Three element tuple, containing the encoded text1, the encoded text2 and 238 the array of unique strings. The zeroth element of the array of unique 239 strings is intentionally blank. 248 def diff_linesToCharsMunge(text):
249 """Split a text into an array of strings. Reduce the texts to a string 250 of hashes where each Unicode character represents one line. 251 Modifies linearray and linehash through being a closure. 253 text: String to encode. 263 while lineEnd < len(text) - 1:
264 lineEnd = delimiter.search(text, lineStart)
267 lineEnd = lineEnd.start()
270 lineEnd = len(text) - 1
272 line = text[lineStart:lineEnd + 1]
275 chars.append(unichr(lineHash[line]))
277 if len(lineArray) == maxLines:
279 line = text[lineStart:]
281 lineArray.append(line)
282 lineHash[line] = len(lineArray) - 1
283 chars.append(unichr(len(lineArray) - 1))
284 lineStart = lineEnd + 1
285 return "".join(chars)
288 maxLines = _g_maximum_lines
289 chars1 = diff_linesToCharsMunge(text1)
290 maxLines = _g_char_limit
291 chars2 = diff_linesToCharsMunge(text2)
292 return (chars1, chars2, lineArray)
296 initial_hash = random.getrandbits( 32 )
298 def get_unique_hash():
300 Generates an unique identifier which can be used to uniquely identify distinct object 309 def pop_dict_last_item(dictionary):
311 Until python 3.5 the popitem() has has a bug where it does not accepts the last=False argument 312 https://bugs.python.org/issue24394 TypeError: popitem() takes no keyword arguments, then, 313 automatically detect which one we have here: 314 https://docs.python.org/3/library/collections.html#collections.OrderedDict.popitem 316 dictionary.popitem(last=
True)
319 {1:
'a'}.popitem(last=
True)
323 def pop_dict_last_item(dictionary):
327 def move_to_dict_beginning(dictionary, key):
329 Move a OrderedDict item to its beginning, or add it to its beginning. 331 Compatible with Python 2.7 332 https://stackoverflow.com/questions/16664874/how-to-add-an-element-to-the-beginning-of-an-ordereddict 336 value = dictionary[key]
338 root = dictionary._OrderedDict__root
341 root[1] = first[0] = dictionary._OrderedDict__map[key] = [root, first, key]
342 dict.__setitem__(dictionary, key, value)
345 dictionary.move_to_end( key, last=
False )
348 def get_relative_path(relative_path, script_file):
350 Computes a relative path for a file on the same folder as this class file declaration. 351 https://stackoverflow.com/questions/4381569/python-os-module-open-file-above-current-directory-with-relative-path 353 basepath = os.path.dirname( script_file )
354 filepath = os.path.abspath( os.path.join( basepath, relative_path ) )
358 def join_path(*args):
359 """ Call join path and then abspath on the result. """ 360 return os.path.abspath( os.path.join( *args ) )
363 def get_duplicated_elements(elements_list):
365 Given an `elements_list` with duplicated elements, return a set only with the duplicated 366 elements in the list. If there are not duplicated elements, an empty set is returned. 368 How do I find the duplicates in a list and create another list with them? 369 https://stackoverflow.com/questions/9835762/how-do-i-find-the-duplicates-in-a-list-and-create-another-list-with-them 371 visited_elements = set()
372 visited_and_duplicated = set()
374 add_item_to_visited_elements = visited_elements.add
375 add_item_to_visited_and_duplicated = visited_and_duplicated.add
377 for item
in elements_list:
379 if item
in visited_elements:
380 add_item_to_visited_and_duplicated(item)
383 add_item_to_visited_elements(item)
385 return visited_and_duplicated
388 def emquote_string(string):
390 Return a string escape into single or double quotes accordingly to its contents. 392 string = str( string )
393 is_single =
"'" in string
394 is_double =
'"' in string
396 if is_single
and is_double:
397 return '"{}"'.format( string.replace(
"'",
"\\'" ) )
400 return '"{}"'.format( string )
402 return "'{}'".format( string )
405 def sort_dictionary_lists(dictionary):
407 Give a dictionary, call `sorted` on all its elements. 410 for key, value
in dictionary.items():
411 dictionary[key] = sorted( value )
416 def sort_alphabetically_and_by_length(iterable):
418 Give an `iterable`, sort its elements accordingly to the following criteria: 419 1. Sorts normally by alphabetical order 420 2. Sorts by descending length 422 How to sort by length of string followed by alphabetical order? 423 https://stackoverflow.com/questions/4659524/how-to-sort-by-length-of-string-followed-by-alphabetical-order 425 return sorted( sorted( natsorted( iterable, key=
lambda item: str( item ).lower() ),
426 key=
lambda item: str( item ).istitle() ),
427 key=
lambda item: len( str( item ) ) )
430 def sort_correctly(iterable):
432 Sort the given iterable in the way that humans expect. 434 How to sort alpha numeric set in python 435 https://stackoverflow.com/questions/2669059/how-to-sort-alpha-numeric-set-in-python 437 convert =
lambda text: int( text )
if text.isdigit()
else text
438 alphanum_key =
lambda key: [convert( characters )
for characters
in re.split(
'([0-9]+)', str( key ).lower() )]
439 return sorted( sorted( iterable, key=alphanum_key ), key=
lambda item: str( item ).istitle() )
442 def sort_dictionary(dictionary):
443 return OrderedDict( sorted( dictionary.items() ) )
446 def sort_dictionaries_on_list(list_of_dictionaries):
447 sorted_dictionaries = []
449 for dictionary
in list_of_dictionaries:
450 sorted_dictionaries.append( sort_dictionary( dictionary ) )
452 return sorted_dictionaries
455 def sort_list_of_dictionaries(list_of_dictionaries):
457 How do I sort a list of dictionaries by values of the dictionary in Python? 458 https://stackoverflow.com/questions/72899/how-do-i-sort-a-list-of-dictionaries-by-values-of-the-dictionary-in-python 460 case-insensitive list sorting, without lowercasing the result? 461 https://stackoverflow.com/questions/10269701/case-insensitive-list-sorting-without-lowercasing-the-result 463 sorted_dictionaries = sort_dictionaries_on_list( list_of_dictionaries )
464 return sorted( sorted_dictionaries, key=
lambda k: k[
'name'].lower() )
467 def get_largest_item_size(iterable):
469 Given a iterable, get the size/length of its largest key value. 475 if len( key ) > largest_key:
476 largest_key = len( key )
481 def dictionary_to_string(dictionary):
483 Given a dictionary with a list for each string key, call `sort_dictionary_lists()` and 484 return a string representation by line of its entries. 487 if not len( dictionary ):
488 return " No elements found." 491 elements_strings = []
493 dictionary = sort_dictionary_lists( dictionary )
494 largest_key = get_largest_item_size( dictionary.keys() ) + 1
496 for key, values
in dictionary.items():
497 elements_strings.clear()
500 elements_strings.append(
"{}".format( str( item ) ) )
502 strings.append(
"{:>{largest_key}}: {}".format( str( key ),
" ".join( elements_strings ),
503 largest_key=largest_key ) )
505 return "\n".join( strings )
508 def convert_to_text_lines(iterable, use_repr=True, new_line=True, sort=None):
510 Given a dictionary with a list for each string key, call `sort_dictionary_lists()` and 511 return a string representation by line of its entries. 514 if isinstance( iterable, dict):
515 return dictionary_to_string( iterable )
518 return " No elements found." 523 iterable = sort( iterable )
526 iterable = sort_alphabetically_and_by_length( iterable )
528 for item
in iterable:
529 strings.append(
" {}".format( repr( item ) ) )
531 return (
"\n" if new_line
else "" ).join( strings )
534 def getCleanSpaces(inputText, minimumLength=0, lineCutTrigger="", keepSpaceSepators=False):
536 Removes spaces and comments from the input expression. 538 `minimumLength` of a line to not be ignored 539 `lineCutTrigger` all lines after a line starting with this string will be ignored 540 `keepSpaceSepators` if True, it will keep at a single space between sentences as `S S`, given `S S` 543 if keepSpaceSepators:
544 removeNewSpaces =
' '.join( inputText.split(
' ' ) )
545 lineCutTriggerNew =
' '.join( lineCutTrigger.split(
' ' ) ).strip(
' ' )
548 removeNewSpaces = re.sub(
r"\t| ",
"", inputText )
549 lineCutTriggerNew = re.sub(
r"\t| ",
"", lineCutTrigger )
552 lines = removeNewSpaces.split(
"\n" )
557 if keepSpaceSepators:
558 line = line.strip(
' ' )
562 if len( line ) < minimumLength:
567 if line.startswith( lineCutTriggerNew ):
570 if line.startswith(
"#" ):
573 clean_lines.append( line )
578 def wrap_text(text, wrap=0, trim_tabs=None, trim_spaces=None, trim_lines=None,
579 trim_plus='+', indent="", initial="", single_lines=False):
581 1. Remove input text leading common indentation, trailing white spaces 582 2. If `wrap`, wraps big lists on 80 characters 583 3. If `trim_tabs`, replace all tabs this value 584 4. If `trim_spaces`, remove this leading symbols 585 5. If `trim_lines`, replace all new line characters by this value 586 5. If `indent`, the subsequent indent to use 587 6. If `trim_plus`, remove this leading symbols 588 7. If `single_lines`, remove single new lines but keep consecutive new lines 592 if not isinstance( text, str ):
595 if trim_tabs
is not None:
596 text = text.replace(
'\t', trim_tabs )
598 dedent_lines = textwrap.dedent( text ).strip(
'\n' )
600 if trim_spaces
and trim_plus:
602 for line
in dedent_lines.split(
'\n' ):
603 line = line.rstrip( trim_spaces ).lstrip( trim_plus )
604 clean_lines.append( line )
608 for line
in dedent_lines.split(
'\n' ):
609 line = line.rstrip( trim_spaces )
610 clean_lines.append( line )
614 for line
in dedent_lines.split(
'\n' ):
615 line = line.lstrip( trim_plus )
616 clean_lines.append( line )
618 if trim_spaces
is not None or trim_plus
is not None:
619 dedent_lines = textwrap.dedent(
"\n".join( clean_lines ) )
624 for line
in dedent_lines.split(
'\n' ):
625 line = textwrap.fill( line, width=wrap, initial_indent=initial, subsequent_indent=indent )
626 clean_lines.append( line )
628 dedent_lines =
"\n".join( clean_lines )
630 if trim_lines
is not None:
631 dedent_lines = trim_lines.join( dedent_lines.split(
'\n' ) )
634 dedent_lines = re.sub(
r'(?<!\n)\n(?!\n)',
' ', dedent_lines)
639 def recursive_get_representation(*args, **kwargs):
640 """ It attempt to detect the `get_representation` function was called recursively 641 It can happen when one attribute contains the other and vice-versa. 643 kwargs `recursive_depth=2` how many recursions levels to dive in 646 recursive_depth = kwargs.pop(
"recursive_depth", 2 )
651 for stack
in traceback.extract_stack()
652 if stack[2] ==
'recursive_get_representation' 656 kwargs[
'is_recursive'] = is_recursive
657 return get_representation(*args, **kwargs)
660 def get_representation(objectname, ignore=[], emquote=False, repr=repr, is_recursive=False):
661 """ Iterating through all its public attributes and return then as a string representation 662 `ignore` a list of attributes to be ignored 663 `emquote` if True, puts the attributes values inside single or double quotes accordingly. 664 `repr` is the callback to call recursively on nested objects, can be either `repr` or `str`. 666 clean_attributes = []
670 def pack_attribute(string):
671 return emquote_string( string )
675 def pack_attribute(string):
678 if hasattr( objectname,
'__dict__' ):
679 valid_attributes = objectname.__dict__.keys()
682 clean_attributes.append(
"{}".format(
'<recursive>' ) )
685 for attribute
in valid_attributes:
687 if not attribute.startswith(
'_' )
and attribute
not in ignore:
688 clean_attributes.append(
"{}: {}".format( attribute, pack_attribute( objectname.__dict__[attribute] ) ) )
690 return "%s %s;" % ( objectname.__class__.__name__,
", ".join( clean_attributes ) )
693 for attribute
in objectname:
694 if attribute
not in ignore:
695 clean_attributes.append(
"{}".format( pack_attribute( attribute ) ) )
697 return "%s" %
", ".join( clean_attributes )
700 def _create_stdout_handler():
702 Call this method to create a copy of this stream model as `stdout_replacement.py` 703 using the `sys.stdout` instead of `sys.stderr`. 705 model_relative_path = get_relative_path(
'stderr_replacement.py', __file__ )
706 destine_relative_path = get_relative_path(
'stdout_replacement.py', __file__ )
708 warning_message = wrap_text(
711 This file is generated automatically based `stderr_replacement.py` while `debug_tools` 712 library/package is being developed. 714 If you developing the `debug_tools` library, please do not edit this file, but the file 715 `stderr_replacement.py` and run `logger.py` function `create_stdout_handler()` by 716 uncommenting it on `all/debug_tools/logger.py` file. 720 sys.stderr.write(
'\nCreating the `stdout_replacement.py` file!\n' )
721 sys.stderr.write(
'model_relative_path %s\n' % model_relative_path )
722 sys.stderr.write(
'destine_relative_path %s\n' % destine_relative_path )
726 with io.open(model_relative_path,
'rb')
as model_file:
727 model_text = model_file.read().decode(
'utf-8')
729 with io.open(destine_relative_path,
'wb')
as destine_file:
730 model_text = model_text.replace(
'stderr',
'stdout' )
731 model_text = model_text.replace(
'# Warning message here', warning_message )
732 destine_file.write( model_text.encode() )