Skip to content

Commit

Permalink
Adds width attribute
Browse files Browse the repository at this point in the history
Adds width attribute which allows user to
specify number of bytes printed per line
  • Loading branch information
Utkarsh1308 committed Jun 13, 2019
1 parent 1e4f7b5 commit 8a64212
Show file tree
Hide file tree
Showing 8 changed files with 260 additions and 22 deletions.
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
74 changes: 59 additions & 15 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 All @@ -121,14 +138,41 @@ def _add_hex_space(self):
self.skipspace = False
else:
self.hexrow += ' '


def final(self):
self.hexrow += 3*(16 - self.rowlen) * ' '
self.asciirow += (16 - self.rowlen) * ' '
self._newrow()
return self.body

def reformat(self, body, n=16):
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):
line = '{:06x}'.format(i*16)
if i == 0:
outstring += '{}:{} {}\n'.format(line, elements[0], chunk)
else:
outstring += '{}: {}\n'.format(line, chunk)
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
32 changes: 28 additions & 4 deletions multidiff/command_line_interface.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
#!/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.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 +29,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 +89,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',
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
79 changes: 79 additions & 0 deletions test/cli_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
from multidiff.Render import *
from multidiff import Ansi

import unittest
from pathlib import Path
import subprocess


class MainCLITests(unittest.TestCase):

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 += "\n000010: "
dump += "36 37 38 39 61 62"
dump += "\n000020: "
dump += "63 64 65 66"
dump += Ansi.delete + " " + Ansi.reset
dump += ' \n\n'
print(dump)

expected_output = dump
self.call_run(expected_output, res)

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

0 comments on commit 8a64212

Please sign in to comment.