11from multidiff .Ansi import Ansi
22import binascii
33import html
4+ import textwrap
5+ import re
46
57class Render ():
6- def __init__ (self , encoder = 'hexdump' , color = 'ansi' ):
8+ def __init__ (self , encoder = 'hexdump' , color = 'ansi' , bytes = 16 , width = None ):
79 '''Configure the output encoding and coloring method of this rendering object'''
810 if color == 'ansi' :
911 self .highligther = ansi_colored
@@ -16,17 +18,22 @@ def __init__(self, encoder='hexdump', color='ansi'):
1618 self .encoder = HexEncoder
1719 elif encoder == 'utf8' :
1820 self .encoder = Utf8Encoder
19-
21+
22+ self .width = width
23+ self .bytes = bytes
24+
2025 def render (self , model , diff ):
2126 '''Render the diff in the given model into a UTF-8 String'''
2227 result = self .encoder (self .highligther )
2328 obj = model .objects [diff .target ]
2429 for op in diff .opcodes :
2530 data = obj .data [op [3 ]:op [4 ]]
2631 if type (data ) == bytes :
27- result .append (data , op [0 ])
32+ result .append (data , op [0 ], self . width , self . bytes )
2833 elif type (data ) == str :
29- result .append (bytes (data , "utf8" ), op [0 ])
34+ result .append (bytes (data , "utf8" ), op [0 ], self .width , self .bytes )
35+ if self .bytes != 16 :
36+ return result .reformat (result .final (), int (self .bytes ))
3037 return result .final ()
3138
3239 def dumps (self , model ):
@@ -42,9 +49,12 @@ def __init__(self, highligther):
4249 self .highligther = highligther
4350 self .output = ''
4451
45- def append (self , data , color ):
52+ def append (self , data , color , width = None , bytes = 16 ):
4653 self .output += self .highligther (str (data , 'utf8' ), color )
47-
54+ if width :
55+ if len (self .output ) > int (width ):
56+ self .output = textwrap .fill (self .output , int (width ))
57+
4858 def final (self ):
4959 return self .output
5060
@@ -53,10 +63,14 @@ class HexEncoder():
5363 def __init__ (self , highligther ):
5464 self .highligther = highligther
5565 self .output = ''
56- def append (self , data , color ):
66+
67+ def append (self , data , color , width = None , bytes = 16 ):
5768 data = str (binascii .hexlify (data ),'utf8' )
5869 self .output += self .highligther (data , color )
59-
70+ if width :
71+ if len (self .output ) > int (width ):
72+ self .output = textwrap .fill (self .output , int (width ))
73+
6074 def final (self ):
6175 return self .output
6276
@@ -71,21 +85,21 @@ def __init__(self, highligther):
7185 self .skipspace = False
7286 self .asciirow = ''
7387
74- def append (self , data , color ):
88+ def append (self , data , color , width = None , bytes = 16 ):
7589 if len (data ) == 0 :
76- self ._append (data , color )
90+ self ._append (data , color , width )
7791 while len (data ) > 0 :
7892 if self .rowlen == 16 :
7993 self ._newrow ()
80- consumed = self ._append (data [:16 - self .rowlen ], color )
94+ consumed = self ._append (data [:16 - self .rowlen ], color , width )
8195 data = data [consumed :]
8296
83- def _append (self , data , color ):
97+ def _append (self , data , color , width ):
8498 if len (data ) == 0 :
8599 #in the case of highlightig a deletion in a target or an
86100 #addition in the source, print a highlighted space and mark
87101 #it skippanble for the next append
88- hexs = ' '
102+ hexs = ' '
89103 self .skipspace = True
90104 else :
91105 self ._add_hex_space ()
@@ -101,7 +115,10 @@ def _append(self, data, color):
101115 asciis += '.'
102116 self .asciirow += self .highligther (asciis , color )
103117
104- self .hexrow += self .highligther (hexs , color )
118+ self .hexrow += self .highligther (hexs , color )
119+ if width :
120+ if len (self .hexrow ) > int (width ):
121+ self .hexrow = textwrap .fill (self .hexrow , int (width ))
105122 self .rowlen += len (data )
106123 return len (data )
107124
@@ -121,14 +138,61 @@ def _add_hex_space(self):
121138 self .skipspace = False
122139 else :
123140 self .hexrow += ' '
124-
141+
125142
126143 def final (self ):
127144 self .hexrow += 3 * (16 - self .rowlen ) * ' '
128145 self .asciirow += (16 - self .rowlen ) * ' '
129146 self ._newrow ()
130147 return self .body
131148
149+ def reformat (self , body , n = 16 ):
150+ asciis = ''
151+ instring = ''
152+ foo = body .split ('\n ' )
153+ for line in foo :
154+ line .rstrip ()
155+ line = line [line .find (':' )+ 1 :line .find ('|' )]
156+ instring += line
157+ instring += '\n '
158+ outstring = ''
159+ # Remove line numbers and newlines.
160+ clean_string = instring .replace (r'\d+:|\n' , '' )
161+ # Split on spaces that are not in tags.
162+ elements = re .split (r'\s+(?![^<]+>)' , clean_string )
163+ # Omit first tag so that everything else can be chunked by n.
164+ clean_elements = elements [1 :]
165+ # Chunk by n.
166+ chunks = [' ' .join (clean_elements [i :i + n ])
167+ for i in range (0 , len (clean_elements ), n )]
168+ # Concatenate the chunks as a line in outstring, with a line number.
169+ for i , chunk in enumerate (chunks ):
170+ asciis = ''
171+ ansi = [Ansi .reset , Ansi .delete , Ansi .replace , Ansi .insert ]
172+ html = ["<span class='delete'>" , "<span class='insert'>" , "<span class='replace'>" , "</span>" ]
173+ res = chunk
174+ if self .highligther == html_colored :
175+ ops = html
176+ else :
177+ ops = ansi
178+ for op in ops :
179+ res = res .replace (op , "" )
180+ res = res .replace (" " , "" )
181+ res = str (binascii .unhexlify (res ), 'utf8' )
182+ res = res .encode ('utf-8' )
183+ #make the ascii dump
184+ for byte in res :
185+ if 0x20 <= byte <= 0x7E :
186+ asciis += chr (byte )
187+ else :
188+ asciis += '.'
189+ addr = '{:06x}' .format (i * n )
190+ if i == 0 :
191+ outstring += '{}:{} {} |{}|\n ' .format (addr , elements [0 ], chunk , asciis )
192+ else :
193+ outstring += '{}: {} |{}|\n ' .format (addr , chunk , asciis )
194+ return outstring
195+
132196def ansi_colored (string , op ):
133197 if op == 'equal' :
134198 return string
0 commit comments