python3 support

This commit is contained in:
wagoodman 2016-12-23 17:23:31 -05:00
parent 3e34e7b53b
commit 4949162dc8

View file

@ -1,330 +1,330 @@
# MIT License # MIT License
# #
# Copyright (c) 2016 Alex Goodman # Copyright (c) 2016 Alex Goodman
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy of # Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in # this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to # the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
# of the Software, and to permit persons to whom the Software is furnished to do # of the Software, and to permit persons to whom the Software is furnished to do
# so, subject to the following conditions: # so, subject to the following conditions:
# #
# The above copyright notice and this permission notice shall be included in all # The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software. # copies or substantial portions of the Software.
# #
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE. # SOFTWARE.
import sys import sys
import difflib import difflib
import argparse import argparse
import StringIO import io
import pygments import pygments
from pygments.lexers import guess_lexer_for_filename from pygments.lexers import guess_lexer_for_filename
from pygments.lexer import RegexLexer from pygments.lexer import RegexLexer
from pygments.formatters import HtmlFormatter from pygments.formatters import HtmlFormatter
from pygments.token import * from pygments.token import *
class DefaultLexer(RegexLexer): class DefaultLexer(RegexLexer):
""" """
Simply lex each line as a token. Simply lex each line as a token.
""" """
name = 'Default' name = 'Default'
aliases = ['default'] aliases = ['default']
filenames = ['*'] filenames = ['*']
tokens = { tokens = {
'root': [ 'root': [
(r'.*\n', Text), (r'.*\n', Text),
] ]
} }
class DiffHtmlFormatter(HtmlFormatter): class DiffHtmlFormatter(HtmlFormatter):
""" """
Formats a single source file with pygments and adds diff highlights based on the Formats a single source file with pygments and adds diff highlights based on the
diff details given. diff details given.
""" """
isLeft = False isLeft = False
diffs = None diffs = None
def __init__(self, isLeft, diffs, *args, **kwargs): def __init__(self, isLeft, diffs, *args, **kwargs):
self.isLeft = isLeft self.isLeft = isLeft
self.diffs = diffs self.diffs = diffs
super(DiffHtmlFormatter, self).__init__(*args, **kwargs) super(DiffHtmlFormatter, self).__init__(*args, **kwargs)
def wrap(self, source, outfile): def wrap(self, source, outfile):
return self._wrap_code(source) return self._wrap_code(source)
def getDiffLineNos(self): def getDiffLineNos(self):
retlinenos = [] retlinenos = []
for idx, ((left_no, left_line),(right_no, right_line),change) in enumerate(self.diffs): for idx, ((left_no, left_line),(right_no, right_line),change) in enumerate(self.diffs):
no = None no = None
if self.isLeft: if self.isLeft:
if change: if change:
if isinstance(left_no, int) and isinstance(right_no, int): if isinstance(left_no, int) and isinstance(right_no, int):
no = '<span class="lineno_q lineno_leftchange">' + str(left_no) + "</span>" no = '<span class="lineno_q lineno_leftchange">' + str(left_no) + "</span>"
elif isinstance(left_no, int) and not isinstance(right_no, int): elif isinstance(left_no, int) and not isinstance(right_no, int):
no = '<span class="lineno_q lineno_leftdel">' + str(left_no) + "</span>" no = '<span class="lineno_q lineno_leftdel">' + str(left_no) + "</span>"
elif not isinstance(left_no, int) and isinstance(right_no, int): elif not isinstance(left_no, int) and isinstance(right_no, int):
no = '<span class="lineno_q lineno_leftadd"> </span>' no = '<span class="lineno_q lineno_leftadd"> </span>'
else: else:
no = '<span class="lineno_q">' + str(left_no) + "</span>" no = '<span class="lineno_q">' + str(left_no) + "</span>"
else: else:
if change: if change:
if isinstance(left_no, int) and isinstance(right_no, int): if isinstance(left_no, int) and isinstance(right_no, int):
no = '<span class="lineno_q lineno_rightchange">' + str(right_no) + "</span>" no = '<span class="lineno_q lineno_rightchange">' + str(right_no) + "</span>"
elif isinstance(left_no, int) and not isinstance(right_no, int): elif isinstance(left_no, int) and not isinstance(right_no, int):
no = '<span class="lineno_q lineno_rightdel"> </span>' no = '<span class="lineno_q lineno_rightdel"> </span>'
elif not isinstance(left_no, int) and isinstance(right_no, int): elif not isinstance(left_no, int) and isinstance(right_no, int):
no = '<span class="lineno_q lineno_rightadd">' + str(right_no) + "</span>" no = '<span class="lineno_q lineno_rightadd">' + str(right_no) + "</span>"
else: else:
no = '<span class="lineno_q">' + str(right_no) + "</span>" no = '<span class="lineno_q">' + str(right_no) + "</span>"
retlinenos.append(no) retlinenos.append(no)
return retlinenos return retlinenos
def _wrap_code(self, source): def _wrap_code(self, source):
source = list(source) source = list(source)
yield 0, '<pre>' yield 0, '<pre>'
for idx, ((left_no, left_line),(right_no, right_line),change) in enumerate(self.diffs): for idx, ((left_no, left_line),(right_no, right_line),change) in enumerate(self.diffs):
#print idx, ((left_no, left_line),(right_no, right_line),change) #print idx, ((left_no, left_line),(right_no, right_line),change)
try: try:
if self.isLeft: if self.isLeft:
if change: if change:
if isinstance(left_no, int) and isinstance(right_no, int) and left_no <= len(source): if isinstance(left_no, int) and isinstance(right_no, int) and left_no <= len(source):
i,t = source[left_no-1] i,t = source[left_no-1]
t = '<span class="left_diff_change">' + t + "</span>" t = '<span class="left_diff_change">' + t + "</span>"
elif isinstance(left_no, int) and not isinstance(right_no, int) and left_no <= len(source): elif isinstance(left_no, int) and not isinstance(right_no, int) and left_no <= len(source):
i,t = source[left_no-1] i,t = source[left_no-1]
t = '<span class="left_diff_del">' + t + "</span>" t = '<span class="left_diff_del">' + t + "</span>"
elif not isinstance(left_no, int) and isinstance(right_no, int): elif not isinstance(left_no, int) and isinstance(right_no, int):
i,t = 1, left_line i,t = 1, left_line
t = '<span class="left_diff_add">' + t + "</span>" t = '<span class="left_diff_add">' + t + "</span>"
else: else:
raise raise
else: else:
if left_no <= len(source): if left_no <= len(source):
i,t = source[left_no-1] i,t = source[left_no-1]
else: else:
i = 1 i = 1
t = left_line t = left_line
else: else:
if change: if change:
if isinstance(left_no, int) and isinstance(right_no, int) and right_no <= len(source): if isinstance(left_no, int) and isinstance(right_no, int) and right_no <= len(source):
i,t = source[right_no-1] i,t = source[right_no-1]
t = '<span class="right_diff_change">' + t + "</span>" t = '<span class="right_diff_change">' + t + "</span>"
elif isinstance(left_no, int) and not isinstance(right_no, int): elif isinstance(left_no, int) and not isinstance(right_no, int):
i,t = 1, right_line i,t = 1, right_line
t = '<span class="right_diff_del">' + t + "</span>" t = '<span class="right_diff_del">' + t + "</span>"
elif not isinstance(left_no, int) and isinstance(right_no, int) and right_no <= len(source): elif not isinstance(left_no, int) and isinstance(right_no, int) and right_no <= len(source):
i,t = source[right_no-1] i,t = source[right_no-1]
t = '<span class="right_diff_add">' + t + "</span>" t = '<span class="right_diff_add">' + t + "</span>"
else: else:
raise raise
else: else:
if right_no <= len(source): if right_no <= len(source):
i,t = source[right_no-1] i,t = source[right_no-1]
else: else:
i = 1 i = 1
t = right_line t = right_line
yield i, t yield i, t
except: except:
#print "WARNING! failed to enumerate diffs fully!" #print "WARNING! failed to enumerate diffs fully!"
pass # this is expected sometimes pass # this is expected sometimes
yield 0, '\n</pre>' yield 0, '\n</pre>'
def _wrap_tablelinenos(self, inner): def _wrap_tablelinenos(self, inner):
dummyoutfile = StringIO.StringIO() dummyoutfile = io.StringIO()
lncount = 0 lncount = 0
for t, line in inner: for t, line in inner:
if t: if t:
lncount += 1 lncount += 1
dummyoutfile.write(line) dummyoutfile.write(line)
fl = self.linenostart fl = self.linenostart
mw = len(str(lncount + fl - 1)) mw = len(str(lncount + fl - 1))
sp = self.linenospecial sp = self.linenospecial
st = self.linenostep st = self.linenostep
la = self.lineanchors la = self.lineanchors
aln = self.anchorlinenos aln = self.anchorlinenos
nocls = self.noclasses nocls = self.noclasses
lines = [] lines = []
for i in self.getDiffLineNos(): for i in self.getDiffLineNos():
lines.append('%s' % (i,)) lines.append('%s' % (i,))
ls = ''.join(lines) ls = ''.join(lines)
# in case you wonder about the seemingly redundant <div> here: since the # in case you wonder about the seemingly redundant <div> here: since the
# content in the other cell also is wrapped in a div, some browsers in # content in the other cell also is wrapped in a div, some browsers in
# some configurations seem to mess up the formatting... # some configurations seem to mess up the formatting...
if nocls: if nocls:
yield 0, ('<table class="%stable">' % self.cssclass + yield 0, ('<table class="%stable">' % self.cssclass +
'<tr><td><div class="linenodiv" ' '<tr><td><div class="linenodiv" '
'style="background-color: #f0f0f0; padding-right: 10px">' 'style="background-color: #f0f0f0; padding-right: 10px">'
'<pre style="line-height: 125%">' + '<pre style="line-height: 125%">' +
ls + '</pre></div></td><td class="code">') ls + '</pre></div></td><td class="code">')
else: else:
yield 0, ('<table class="%stable">' % self.cssclass + yield 0, ('<table class="%stable">' % self.cssclass +
'<tr><td class="linenos"><div class="linenodiv"><pre>' + '<tr><td class="linenos"><div class="linenodiv"><pre>' +
ls + '</pre></div></td><td class="code">') ls + '</pre></div></td><td class="code">')
yield 0, dummyoutfile.getvalue() yield 0, dummyoutfile.getvalue()
yield 0, '</td></tr></table>' yield 0, '</td></tr></table>'
class CodeDiff(object): class CodeDiff(object):
""" """
Manages a pair of source files and generates a single html diff page comparing Manages a pair of source files and generates a single html diff page comparing
the contents. the contents.
""" """
pygmentsStyleOpt = "vs" pygmentsStyleOpt = "vs"
pygmentsCssFile="./deps/codeformats/%s.css" % pygmentsStyleOpt pygmentsCssFile="./deps/codeformats/%s.css" % pygmentsStyleOpt
diffCssFile="./deps/diff.css" diffCssFile="./deps/diff.css"
diffJsFile="./deps/diff.js" diffJsFile="./deps/diff.js"
resetCssFile="./deps/reset.css" resetCssFile="./deps/reset.css"
jqueryJsFile="./deps/jquery.min.js" jqueryJsFile="./deps/jquery.min.js"
def __init__(self, fromfile, tofile, fromtxt=None, totxt=None, name=None): def __init__(self, fromfile, tofile, fromtxt=None, totxt=None, name=None):
self.filename = name self.filename = name
self.fromfile = fromfile self.fromfile = fromfile
if fromtxt == None: if fromtxt == None:
try: try:
with open(fromfile) as f: with open(fromfile) as f:
self.fromlines = f.readlines() self.fromlines = f.readlines()
except Exception as e: except Exception as e:
print "Problem reading file %s" % fromfile print("Problem reading file %s" % fromfile)
print e print(e)
sys.exit(1) sys.exit(1)
else: else:
self.fromlines = [n + "\n" for n in fromtxt.split("\n")] self.fromlines = [n + "\n" for n in fromtxt.split("\n")]
self.leftcode = "".join(self.fromlines) self.leftcode = "".join(self.fromlines)
self.tofile = tofile self.tofile = tofile
if totxt == None: if totxt == None:
try: try:
with open(tofile) as f: with open(tofile) as f:
self.tolines = f.readlines() self.tolines = f.readlines()
except Exception as e: except Exception as e:
print "Problem reading file %s" % tofile print("Problem reading file %s" % tofile)
print e print(e)
sys.exit(1) sys.exit(1)
else: else:
self.tolines = [n + "\n" for n in totxt.split("\n")] self.tolines = [n + "\n" for n in totxt.split("\n")]
self.rightcode = "".join(self.tolines) self.rightcode = "".join(self.tolines)
def getDiffDetails(self, fromdesc='', todesc='', context=False, numlines=5, tabSize=8): def getDiffDetails(self, fromdesc='', todesc='', context=False, numlines=5, tabSize=8):
# change tabs to spaces before it gets more difficult after we insert # change tabs to spaces before it gets more difficult after we insert
# markkup # markkup
def expand_tabs(line): def expand_tabs(line):
# hide real spaces # hide real spaces
line = line.replace(' ','\0') line = line.replace(' ','\0')
# expand tabs into spaces # expand tabs into spaces
line = line.expandtabs(tabSize) line = line.expandtabs(tabSize)
# replace spaces from expanded tabs back into tab characters # replace spaces from expanded tabs back into tab characters
# (we'll replace them with markup after we do differencing) # (we'll replace them with markup after we do differencing)
line = line.replace(' ','\t') line = line.replace(' ','\t')
return line.replace('\0',' ').rstrip('\n') return line.replace('\0',' ').rstrip('\n')
self.fromlines = [expand_tabs(line) for line in self.fromlines] self.fromlines = [expand_tabs(line) for line in self.fromlines]
self.tolines = [expand_tabs(line) for line in self.tolines] self.tolines = [expand_tabs(line) for line in self.tolines]
# create diffs iterator which generates side by side from/to data # create diffs iterator which generates side by side from/to data
if context: if context:
context_lines = numlines context_lines = numlines
else: else:
context_lines = None context_lines = None
diffs = difflib._mdiff(self.fromlines, self.tolines, context_lines, linejunk=None, charjunk=difflib.IS_CHARACTER_JUNK) diffs = difflib._mdiff(self.fromlines, self.tolines, context_lines, linejunk=None, charjunk=difflib.IS_CHARACTER_JUNK)
return list(diffs) return list(diffs)
def format(self, verbose=False): def format(self, verbose=False):
self.diffs = self.getDiffDetails(self.fromfile, self.tofile) self.diffs = self.getDiffDetails(self.fromfile, self.tofile)
if verbose: if verbose:
for diff in self.diffs: for diff in self.diffs:
print "%-6s %-80s %-80s" % ( diff[2], diff[0], diff[1] ) print("%-6s %-80s %-80s" % ( diff[2], diff[0], diff[1] ))
fields = ( (self.leftcode, True, self.fromfile) , (self.rightcode, False, self.tofile) ) fields = ( (self.leftcode, True, self.fromfile) , (self.rightcode, False, self.tofile) )
codeContents = [] codeContents = []
for (code, isLeft, filename) in fields: for (code, isLeft, filename) in fields:
inst = DiffHtmlFormatter(isLeft, inst = DiffHtmlFormatter(isLeft,
self.diffs, self.diffs,
nobackground=False, nobackground=False,
linenos=True, linenos=True,
style=self.pygmentsStyleOpt) style=self.pygmentsStyleOpt)
try: try:
self.lexer = guess_lexer_for_filename(self.filename, code) self.lexer = guess_lexer_for_filename(self.filename, code)
except pygments.util.ClassNotFound: except pygments.util.ClassNotFound:
if verbose: if verbose:
print "No Lexer Found! Using default..." print("No Lexer Found! Using default...")
self.lexer = DefaultLexer() self.lexer = DefaultLexer()
formatted = pygments.highlight(code, self.lexer, inst) formatted = pygments.highlight(code, self.lexer, inst)
codeContents.append(formatted) codeContents.append(formatted)
diffTemplate = open("./templates/diff_template.html",'r').read() diffTemplate = open("./templates/diff_template.html",'r').read()
answers = { answers = {
"html_title": self.filename, "html_title": self.filename,
"reset_css": self.resetCssFile, "reset_css": self.resetCssFile,
"pygments_css": self.pygmentsCssFile, "pygments_css": self.pygmentsCssFile,
"diff_css": self.diffCssFile, "diff_css": self.diffCssFile,
"page_title": self.filename, "page_title": self.filename,
"original_code": codeContents[0], "original_code": codeContents[0],
"modified_code": codeContents[1], "modified_code": codeContents[1],
"jquery_js": self.jqueryJsFile, "jquery_js": self.jqueryJsFile,
"diff_js": self.diffJsFile, "diff_js": self.diffJsFile,
} }
self.htmlContents = diffTemplate % answers self.htmlContents = diffTemplate % answers
def write(self, path="index.html"): def write(self, path="index.html"):
fh = open(path,'w') fh = open(path,'w')
fh.write(self.htmlContents.encode('utf8')) fh.write(self.htmlContents.encode('utf8'))
fh.close() fh.close()
def main(fromfile, tofile, verbose=False): def main(fromfile, tofile, verbose=False):
codeDiff = CodeDiff(fromfile, tofile, name=tofile) codeDiff = CodeDiff(fromfile, tofile, name=tofile)
codeDiff.format(verbose) codeDiff.format(verbose)
codeDiff.write() codeDiff.write()
if __name__ == "__main__": if __name__ == "__main__":
description = """Given two source files this application\ description = """Given two source files this application\
creates an html page which highlights the differences between the two. """ creates an html page which highlights the differences between the two. """
parser = argparse.ArgumentParser(description=description) parser = argparse.ArgumentParser(description=description)
parser.add_argument('-v', action='store_true', help='show verbose output.') parser.add_argument('-v', action='store_true', help='show verbose output.')
parser.add_argument('file1', help='source file to compare ("before" file).') parser.add_argument('file1', help='source file to compare ("before" file).')
parser.add_argument('file2', help='source file to compare ("after" file).') parser.add_argument('file2', help='source file to compare ("after" file).')
args = parser.parse_args() args = parser.parse_args()
main(args.file1, args.file2, args.v) main(args.file1, args.file2, args.v)