Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add width attribute #8

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ python:
- 3.5
- 3.4.2
install:
- pip install git+https://github.com/juhakivekas/multidiff
- pip install .
script:
- python -m pytest
92 changes: 78 additions & 14 deletions multidiff/Render.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from multidiff.Ansi import Ansi
import binascii
import html
import textwrap
import re

class Render():
def __init__(self, encoder='hexdump', color='ansi'):
def __init__(self, encoder='hexdump', color='ansi', bytes=16, width=None):
'''Configure the output encoding and coloring method of this rendering object'''
if color == 'ansi':
self.highligther = ansi_colored
Expand All @@ -16,17 +18,22 @@ def __init__(self, encoder='hexdump', color='ansi'):
self.encoder = HexEncoder
elif encoder == 'utf8':
self.encoder = Utf8Encoder


self.width = width
self.bytes = bytes

def render(self, model, diff):
'''Render the diff in the given model into a UTF-8 String'''
result = self.encoder(self.highligther)
obj = model.objects[diff.target]
for op in diff.opcodes:
data = obj.data[op[3]:op[4]]
if type(data) == bytes:
result.append(data, op[0])
result.append(data, op[0], self.width, self.bytes)
elif type(data) == str:
result.append(bytes(data, "utf8"), op[0])
result.append(bytes(data, "utf8"), op[0], self.width, self.bytes)
if self.bytes != 16:
return result.reformat(result.final(), int(self.bytes))
return result.final()

def dumps(self, model):
Expand All @@ -42,9 +49,12 @@ def __init__(self, highligther):
self.highligther = highligther
self.output = ''

def append(self, data, color):
def append(self, data, color, width=None, bytes=16):
self.output += self.highligther(str(data, 'utf8'), color)

if width:
if len(self.output) > int(width):
self.output = textwrap.fill(self.output, int(width))

def final(self):
return self.output

Expand All @@ -53,10 +63,14 @@ class HexEncoder():
def __init__(self, highligther):
self.highligther = highligther
self.output = ''
def append(self, data, color):

def append(self, data, color, width=None, bytes=16):
data = str(binascii.hexlify(data),'utf8')
self.output += self.highligther(data, color)

if width:
if len(self.output) > int(width):
self.output = textwrap.fill(self.output, int(width))

def final(self):
return self.output

Expand All @@ -71,21 +85,21 @@ def __init__(self, highligther):
self.skipspace = False
self.asciirow = ''

def append(self, data, color):
def append(self, data, color, width=None, bytes=16):
if len(data) == 0:
self._append(data, color)
self._append(data, color, width)
while len(data) > 0:
if self.rowlen == 16:
self._newrow()
consumed = self._append(data[:16 - self.rowlen], color)
consumed = self._append(data[:16 - self.rowlen], color, width)
data = data[consumed:]

def _append(self, data, color):
def _append(self, data, color, width):
if len(data) == 0:
#in the case of highlightig a deletion in a target or an
#addition in the source, print a highlighted space and mark
#it skippanble for the next append
hexs = ' '
hexs = ' '
self.skipspace = True
else:
self._add_hex_space()
Expand All @@ -101,7 +115,10 @@ def _append(self, data, color):
asciis += '.'
self.asciirow += self.highligther(asciis, color)

self.hexrow += self.highligther(hexs, color)
self.hexrow += self.highligther(hexs, color)
if width:
if len(self.hexrow) > int(width):
self.hexrow = textwrap.fill(self.hexrow, int(width))
self.rowlen += len(data)
return len(data)

Expand Down Expand Up @@ -129,6 +146,53 @@ def final(self):
self._newrow()
return self.body

def reformat(self, body, n=16):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't need to be this complicated. Just find the number 16 from Render.py and make it not hardcoded. For example replace all occurencies of 16 with self.bytewidth and set that variable when constructing the Render object. This way there's no need to compile the regular expressions, find strings and do all the reformatting that takes a lot of time (you mentioned this was an issue?). As a side effect the format (addresses, ascii conversion etc.) will match the original

Copy link
Contributor Author

@Utkarsh1308 Utkarsh1308 Jun 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I tried the approach you are suggesting first but then the code runs slowly on my machine. I don't know if it's a problem with my machine or not. Please tell me if by changing those lines the code runs the same in your machine. I would be happy to implement that since it's easier :)

asciis = ''
instring = ''
foo = body.split('\n')
for line in foo:
line.rstrip()
line = line[line.find(':')+1:line.find('|')]
instring += line
instring += '\n'
outstring = ''
# Remove line numbers and newlines.
clean_string = instring.replace(r'\d+:|\n', '')
# Split on spaces that are not in tags.
elements = re.split(r'\s+(?![^<]+>)', clean_string)
# Omit first tag so that everything else can be chunked by n.
clean_elements = elements[1:]
# Chunk by n.
chunks = [' '.join(clean_elements[i:i+n])
for i in range(0, len(clean_elements), n)]
# Concatenate the chunks as a line in outstring, with a line number.
for i, chunk in enumerate(chunks):
asciis = ''
ansi = [Ansi.reset, Ansi.delete, Ansi.replace, Ansi.insert]
html = ["<span class='delete'>", "<span class='insert'>", "<span class='replace'>", "</span>"]
res = chunk
if self.highligther == html_colored:
ops = html
else:
ops = ansi
for op in ops:
res = res.replace(op, "")
res = res.replace(" ", "")
res = str(binascii.unhexlify(res), 'utf8')
res = res.encode('utf-8')
#make the ascii dump
for byte in res:
if 0x20 <= byte <= 0x7E:
asciis += chr(byte)
else:
asciis += '.'
addr = '{:06x}'.format(i*n)
if i == 0:
outstring += '{}:{} {} |{}|\n'.format(addr, elements[0], chunk, asciis)
else:
outstring += '{}: {} |{}|\n'.format(addr, chunk, asciis)
return outstring

def ansi_colored(string, op):
if op == 'equal':
return string
Expand Down
5 changes: 3 additions & 2 deletions multidiff/StreamView.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
class StreamView():
'''A class for building UIs. Has some pretty serious side effects.
Use Render instead if you're not making a long-running UI'''
def __init__(self, model, encoding='hexdump', mode='sequence', color='ansi'):
def __init__(self, model, encoding='hexdump', mode='sequence', color='ansi', bytes=16, width=None):
self.color = color
self.render = Render(color=color, encoder=encoding)
self.render = Render(color=color, encoder=encoding, bytes=bytes, width=width)
self.mode = mode
self.model = model
self.bytes = bytes
model.add_listener(self)

def object_added(self, index):
Expand Down
33 changes: 30 additions & 3 deletions multidiff/command_line_interface.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
#!/usr/bin/python3
import argparse
from multidiff import MultidiffModel, StreamView, SocketController, FileController, StdinController
import shutil
import sys

def main():
args = make_parser().parse_args()
def main(args=None):

if args is None:
args = sys.argv[1:]
args = make_parser().parse_args(args)
m = MultidiffModel()
v = StreamView(m, encoding=args.outformat, mode=args.mode, color=args.color)

if args.bytes != 16:
args.width = 'max'

if args.width == 'max':
args.width = get_max_width(args)

v = StreamView(m, encoding=args.outformat, mode=args.mode, color=args.color, bytes=args.bytes, width=args.width)

if len(args.file) > 0:
informat = args.informat if args.informat else 'raw'
files = FileController(m, informat)
Expand All @@ -20,6 +32,11 @@ def main():
server = SocketController(('127.0.0.1', args.port), m, informat)
server.serve_forever()

def get_max_width(args):
columns = int(shutil.get_terminal_size((120,30)).columns)
args.width = columns
return args.width

def make_parser():
parser = argparse.ArgumentParser(
formatter_class=argparse.RawTextHelpFormatter,
Expand Down Expand Up @@ -75,6 +92,16 @@ def make_parser():
const='html',
default='ansi',
help='use html for colors instead of ansi codes')

parser.add_argument('-w', '--width',
dest='width',
default='82',
Utkarsh1308 marked this conversation as resolved.
Show resolved Hide resolved
help='number of bytes printed per line, either an integer or max(width of console)')

parser.add_argument('-b', '--bytes',
dest='bytes',
default=16,
help='number of hexs printed per line, either an integer or max(width of console)')
return parser

if __name__ == '__main__':
Expand Down
1 change: 1 addition & 0 deletions test/bin_file1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0123456789abcdef012345678
1 change: 1 addition & 0 deletions test/bin_file2
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0123456789abcdef
82 changes: 82 additions & 0 deletions test/cli_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from multidiff.Render import *
from multidiff import Ansi

import unittest
from pathlib import Path
import subprocess


class MainCLITests(unittest.TestCase):
Utkarsh1308 marked this conversation as resolved.
Show resolved Hide resolved

def call_run(self, expected_stdout, args):
got_stdout = '(no stdout)'
cmd = ['multidiff'] + args
try:
got_stdout = subprocess.check_output(cmd, universal_newlines=True)
except subprocess.CalledProcessError as err:
print('Got stderr: `{err_message}`'.format(err_message=err))
finally:
print('Got stdout: `{stdout}`'.format(stdout=got_stdout))

self.assertEqual(expected_stdout, got_stdout)

def test_diff_cli_no_args(self):
expected_output = ''
self.call_run(expected_output, [])

def test_diff_cli_simple(self):
p = Path(".")
res = list(p.glob("**/bin_file*"))
res = [str(x) for x in res]

dump = Ansi.bold + res[1] + Ansi.reset
dump += "\n000000: "
dump += "30 31 32 33 34 35 36 37 38 39 61 62 63 64 65 66"
dump += Ansi.delete + " " + Ansi.reset
dump += "|0123456789abcdef|\n"

expected_output = dump
self.call_run(expected_output, res)

def test_diff_cli_with_width_flag(self):
p = Path('.')
res = res = list(p.glob('**/bin_file*'))
res = [str(x) for x in res]
res += ['--width', '25']

dump = Ansi.bold + res[1] + Ansi.reset
dump += "\n000000: "
dump += "30 31 32 33 34 35 36 37"
dump += "\n38 39 61 62 63 64 65"
dump += "\n66"
dump += Ansi.delete + " " + Ansi.reset
dump += "|0123456789abcdef|\n"

expected_output = dump
self.call_run(expected_output, res)

def test_diff_cli_with_bytes_flag(self):
p = Path('.')
res = res = list(p.glob('**/bin_file*'))
res = [str(x) for x in res]
res += ['--bytes', '6']

dump = Ansi.bold + res[1] + Ansi.reset
dump += "\n000000: "
dump += "30 31 32 33 34 35"
dump += ' |012345|'
dump += "\n000006: "
dump += "36 37 38 39 61 62"
dump += ' |6789ab|'
dump += "\n00000c: "
dump += "63 64 65 66"
dump += Ansi.delete + " " + Ansi.reset
dump += ' |cdef|'
dump += '\n\n'
print(dump)

expected_output = dump
self.call_run(expected_output, res)

if __name__ == '__main__':
unittest.main()
Loading