Skip to content

Commit

Permalink
Iterator and formatters reworked. Again.
Browse files Browse the repository at this point in the history
  • Loading branch information
mr-mixas committed Dec 13, 2019
1 parent 13516c1 commit 1f1229d
Show file tree
Hide file tree
Showing 9 changed files with 246 additions and 170 deletions.
95 changes: 51 additions & 44 deletions nested_diff/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -569,95 +569,102 @@ def __init__(self, sort_keys=False):
"""
self.sort_keys = sort_keys

self.__iters = {
dict: self._iter_mapping,
list: self._iter_sequence,
tuple: self._iter_sequence,
self.__iterators = {
dict: self.iterate_mapping_diff,
list: self.iterate_sequence_diff,
tuple: self.iterate_sequence_diff,
}

def _iter_mapping(self, value):
@staticmethod
def iterate__default(ndiff):
"""
Iterate over dict-like objects.
Yield final diff (do not iterate deeper).
:param ndiff: nested diff.
:param value: mapping.
"""
yield ndiff, None, None

def iterate_mapping_diff(self, ndiff):
"""
items = sorted(value.items()) if self.sort_keys else value.items()
type_ = value.__class__
Iterate over dict-like nested diffs.
for key, val in items:
yield type_, key, val
:param ndiff: nested diff.
"""
items = ndiff['D'].items()

for key, subdiff in sorted(items) if self.sort_keys else items:
yield ndiff, key, subdiff

@staticmethod
def _iter_sequence(value):
def iterate_sequence_diff(ndiff):
"""
Iterate over lists, tuples and other sequences.
Iterate over lists, tuples and alike nedsted diffs.
:param value: sequence.
:param ndiff: nested diff.
"""
idx = 0
type_ = value.__class__

for item in value:
for item in ndiff['D']:
if 'I' in item:
idx = item['I']

yield type_, idx, item
yield ndiff, idx, item

idx += 1

def get_iter(self, value):
def get_iterator(self, ndiff):
"""
Return apropriate iterator for passed diff value.
Return apropriate iterator for passed nested diff.
:param ndiff: nested diff.
"""
if 'E' in ndiff:
return self.iterate__default(ndiff)

try:
return self.__iters[value.__class__](value)
return self.__iterators[ndiff['D'].__class__](ndiff)
except KeyError:
raise NotImplementedError from None
return self.iterate__default(ndiff)

def set_iter(self, type_, method):
def set_iterator(self, type_, method):
"""
Set generator for specified data type.
Set generator for specified nested diff type.
:param type_: data type.
:param type_: type.
:param method: method.
Generator should yield tuples with three items: container_type, pointer
and subdiff.
Generator should yield tuples with three items: diff, key, and
subdiff for this key.
"""
self.__iters[type_] = method
self.__iterators[type_] = method

def iterate(self, ndiff, depth=0):
def iterate(self, ndiff):
"""
Return tuples with depth, container_type, pointer and subdiff for each
nested diff.
Yield tuples with diff, key and subdiff for each nested diff.
:param ndiff: Nested diff.
:param ndiff: nested diff.
"""
stack = [((None, None, _) for _ in (ndiff,))]
stack = [self.get_iterator(ndiff)]

while True:
while stack:
try:
container_type, pointer, subdiff = next(stack[-1])
diff, key, subdiff = next(stack[-1])
except StopIteration:
stack.pop()
continue

if stack:
depth -= 1
continue
else:
break
yield diff, key, subdiff

yield depth, container_type, pointer, subdiff
if subdiff is None:
continue

if 'D' in subdiff:
if 'E' not in subdiff:
stack.append(self.get_iter(subdiff['D']))
depth += 1
stack.append(self.get_iterator(subdiff))


def diff(a, b, **kwargs):
Expand Down
136 changes: 72 additions & 64 deletions nested_diff/fmt.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,16 @@ def __init__(
self.diff_key_tokens = {
'A': '+ ',
'D': ' ',
'I': ' ',
'N': '+ ',
'O': '- ',
'N': ' ',
'O': ' ',
'R': '- ',
'U': ' ',
}
self.diff_value_tokens = self.diff_key_tokens.copy()
self.diff_value_tokens['H'] = '# '
self.diff_value_tokens['I'] = ' '
self.diff_value_tokens['O'] = '- '
self.diff_value_tokens['N'] = '+ '

self.tags = ( # diff tags to format, sequence is important
'D',
Expand Down Expand Up @@ -138,7 +141,7 @@ def set_close_token(self, type_, token):

class TextFormatter(AbstractFormatter):
"""
Produce human friendly text diff representation with indenting formatting.
Produce human friendly text diff with indenting formatting.
"""
def __init__(self, *args, **kwargs):
Expand All @@ -155,11 +158,13 @@ def emit_miltiline_tokens(self, diff, depth=0):
Yield unified diff for multiline strings.
"""
indent = self.indent * depth

for subdiff in diff['D']:
for tag in ('I', 'R', 'A', 'U'):
if tag in subdiff:
yield self.diff_value_tokens[tag]
yield self.indent * depth
yield indent
if tag == 'I':
yield '@@'
yield ' -' + self.get_unified_diff_range(
Expand All @@ -176,33 +181,24 @@ def emit_set_tokens(self, diff, depth=0):
Yield tokens for set's and frozenset's diff
"""
yield self.diff_key_tokens['D']
yield self.indent * depth
yield '<'
yield diff['E'].__class__.__name__
yield '>'
yield self.line_separator

depth += 1
indent = self.indent * depth

for subdiff in diff['D']:
for tag in ('R', 'A', 'U'):
if tag in subdiff:
yield self.diff_value_tokens[tag]
yield self.indent * depth
yield indent
yield self.repr_value(subdiff[tag])
yield self.line_separator
break

def get_emitter(self, diff, depth=0):
"""
Return apropriate tokens emitter for diff extention.
"""
try:
return self.__emitters[diff['E'].__class__](diff, depth=depth)
except KeyError:
raise NotImplementedError from None
def emit_type_header(self, diff, depth=0):
yield self.diff_value_tokens['H']
yield self.indent * depth
yield '<'
yield diff['E'].__class__.__name__
yield '>'
yield self.line_separator

def emit_tokens(self, diff, depth=0, header='', footer=''):
"""
Expand All @@ -213,40 +209,60 @@ def emit_tokens(self, diff, depth=0, header='', footer=''):
yield header
yield self.line_separator

key_tag = 'D'

for depth, container_type, pointer, diff in self.iterate(
diff, depth=depth):

for tag in self.tags:
if tag in diff:
# key/index
if key_tag is None:
key_tag = 'D' if tag in ('O', 'N') else tag
yield self.diff_key_tokens[key_tag]
yield self.indent * (depth - 1)
yield self.get_open_token(container_type)
yield self.repr_key(pointer)
yield self.get_close_token(container_type)
stack = [self.get_iterator(diff)]

while stack:
try:
diff, key, subdiff = next(stack[-1])
except StopIteration:
stack.pop()
depth -= 1
continue

# emit value
if 'E' in diff:
yield from self.emit_type_header(diff, depth=depth)
yield from self.get_emitter(diff, depth=depth)
continue

if subdiff is None:
for tag in self.tags:
if tag in diff:
yield self.diff_value_tokens[tag]
yield self.indent * depth
yield self.repr_value(diff[tag])
yield self.line_separator
continue

# value
if tag == 'D':
if 'E' in diff:
yield from self.get_emitter(diff, depth=depth)
break

yield self.diff_value_tokens[tag]
# emit key
diff_type = diff['D'].__class__
for tag in self.tags:
if tag in subdiff:
yield self.diff_key_tokens[tag]
yield self.indent * depth
yield self.repr_value(diff[tag])
yield self.get_open_token(diff_type)
yield self.repr_key(key)
yield self.get_close_token(diff_type)
yield self.line_separator
break

key_tag = None
depth += 1
stack.append(self.get_iterator(subdiff))

if footer:
yield footer
yield self.line_separator

def get_emitter(self, diff, depth=0):
"""
Return apropriate tokens emitter for diff extention.
"""
try:
return self.__emitters[diff['E'].__class__](diff, depth=depth)
except KeyError:
raise NotImplementedError from None


class TermFormatter(TextFormatter):
"""
Expand All @@ -258,20 +274,12 @@ def __init__(self, *args, **kwargs):

self.line_separator = '\033[0m' + self.line_separator

self.diff_key_tokens = {
'A': '\033[1;32m+ ',
'D': ' ',
'N': '\033[1;32m+ ',
'O': '\033[1;31m- ',
'R': '\033[1;31m- ',
'U': ' ',
}
self.diff_value_tokens = {
'A': '\033[32m+ ',
'D': ' ',
'I': '\033[35m ',
'N': '\033[32m+ ',
'O': '\033[31m- ',
'R': '\033[31m- ',
'U': ' ',
}
self.diff_key_tokens['A'] = '\033[1;32m' + self.diff_key_tokens['A']
self.diff_key_tokens['R'] = '\033[1;31m' + self.diff_key_tokens['R']

self.diff_value_tokens['A'] = '\033[32m' + self.diff_value_tokens['A']
self.diff_value_tokens['H'] = '\033[34m' + self.diff_value_tokens['H']
self.diff_value_tokens['I'] = '\033[35m' + self.diff_value_tokens['I']
self.diff_value_tokens['N'] = '\033[32m' + self.diff_value_tokens['N']
self.diff_value_tokens['O'] = '\033[31m' + self.diff_value_tokens['O']
self.diff_value_tokens['R'] = '\033[31m' + self.diff_value_tokens['R']
1 change: 1 addition & 0 deletions tests/cli/test_diff_tool.test_multiline_context_0.exp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{'text'}
# <str>
@@ -5,4 +5,4 @@
- First string for a.
- Second striing for a.
Expand Down
1 change: 1 addition & 0 deletions tests/cli/test_diff_tool.test_multiline_default.exp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{'text'}
# <str>
@@ -2,13 +2,13 @@
another common line.
And one more common line.
Expand Down
1 change: 1 addition & 0 deletions tests/cli/test_diff_tool.test_multiline_default_term.exp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{'text'}
# <str>
 @@ -2,13 +2,13 @@
another common line.
And one more common line.
Expand Down
Loading

0 comments on commit 1f1229d

Please sign in to comment.