Skip to content

Instantly share code, notes, and snippets.

@oprypin
Last active October 22, 2023 01:34
Show Gist options
  • Save oprypin/9668969 to your computer and use it in GitHub Desktop.
Save oprypin/9668969 to your computer and use it in GitHub Desktop.
Diffs with syntax highlight
import difflib
import pygments
import pygments.lexers
import pygments.formatters
def read_file(fn):
with open(fn) as f:
return f.read()
file_names = ['example_a.py', 'example_b.py']
file_contents = [read_file(fn) for fn in file_names]
# Override HtmlFormatter's wrap function so it
# doesn't wrap our result in excess HTML containers
class Formatter(pygments.formatters.HtmlFormatter):
def wrap(self, source, outfile):
return source
# Highlight both files with Pygments (guessing lexer by filename and code)
colored = [pygments.highlight(fc,
pygments.lexers.guess_lexer_for_filename(fn, fc),
Formatter()
).splitlines(keepends=True) for fn, fc in zip(file_names, file_contents)]
# Also, difflib wants the inputs as separate lines with newlines at the end
with open('out.html', 'w') as out_file:
# Write the needed HTML to enable styles
out_file.write('<head><link rel="stylesheet" type="text/css" href="style.css"></head>\n')
out_file.write('<pre class="code">')
# Get the diff line by line
# The 4 arguments are 2 files and their 2 names
for line in difflib.unified_diff(*(colored+file_names)):
# For each line we output a <div> with the original content,
# but if it starts with a special diff symbol, we apply a class to it
cls = {'+': 'diff_plus', '-': 'diff_minus', '@': 'diff_special'}.get(line[:1], '')
if cls:
cls = ' class="{}"'.format(cls)
line = '<div{}>{}</div>'.format(cls, line.rstrip('\n'))
out_file.write(line)
out_file.write('</pre>')
def hello()
print("Hello, World!")
hello()
def hello(name="world")
name = name.upper()
print("Hello, {name}!".format(name))
hello()
.code .diff_minus { background-color: rgba(255, 0, 0, 0.3) }
.code .diff_plus { background-color: rgba(0, 255, 0, 0.3) }
.code .diff_special { background-color: rgba(128, 128, 128, 0.3) }
.code .hll { background-color: #ffffcc }
.code .c { color: #408080; font-style: italic }
.code .err { border: 1px solid #FF0000 }
.code .k { color: #008000; font-weight: bold }
.code .o { color: #666666 }
.code .cm { color: #408080; font-style: italic }
.code .cp { color: #BC7A00 }
.code .c1 { color: #408080; font-style: italic }
.code .cs { color: #408080; font-style: italic }
.code .gd { color: #A00000 }
.code .ge { font-style: italic }
.code .gr { color: #FF0000 }
.code .gh { color: #000080; font-weight: bold }
.code .gi { color: #00A000 }
.code .go { color: #808080 }
.code .gp { color: #000080; font-weight: bold }
.code .gs { font-weight: bold }
.code .gu { color: #800080; font-weight: bold }
.code .gt { color: #0040D0 }
.code .kc { color: #008000; font-weight: bold }
.code .kd { color: #008000; font-weight: bold }
.code .kn { color: #008000; font-weight: bold }
.code .kp { color: #008000 }
.code .kr { color: #008000; font-weight: bold }
.code .kt { color: #B00040 }
.code .m { color: #666666 }
.code .s { color: #BA2121 }
.code .na { color: #7D9029 }
.code .nb { color: #008000 }
.code .nc { color: #0000FF; font-weight: bold }
.code .no { color: #880000 }
.code .nd { color: #AA22FF }
.code .ni { color: #999999; font-weight: bold }
.code .ne { color: #D2413A; font-weight: bold }
.code .nf { color: #0000FF }
.code .nl { color: #A0A000 }
.code .nn { color: #0000FF; font-weight: bold }
.code .nt { color: #008000; font-weight: bold }
.code .nv { color: #19177C }
.code .ow { color: #AA22FF; font-weight: bold }
.code .w { color: #bbbbbb }
.code .mf { color: #666666 }
.code .mh { color: #666666 }
.code .mi { color: #666666 }
.code .mo { color: #666666 }
.code .sb { color: #BA2121 }
.code .sc { color: #BA2121 }
.code .sd { color: #BA2121; font-style: italic }
.code .s2 { color: #BA2121 }
.code .se { color: #BB6622; font-weight: bold }
.code .sh { color: #BA2121 }
.code .si { color: #BB6688; font-weight: bold }
.code .sx { color: #008000 }
.code .sr { color: #BB6688 }
.code .s1 { color: #BA2121 }
.code .ss { color: #19177C }
.code .bp { color: #008000 }
.code .vc { color: #19177C }
.code .vg { color: #19177C }
.code .vi { color: #19177C }
.code .il { color: #666666 }
@CyberShadow
Copy link

CyberShadow commented Apr 11, 2018

Here's a variant of this for output in a terminal:

#!/bin/bash
set -euo pipefail

# Diff two files with syntax highlighting using pygmentize.

# pygmentize-term should be a pygmentize wrapper with your preferred
# pygmentize settings (formatter / style / filters)

function prepare() {
	local fn=$1

	# Work around https://bitbucket.org/birkenfeld/pygments-main/issues/1437
	if [[ "$fn" == /dev/null ]]
	then
		return
	fi

	local lexer
	lexer=$(pygmentize -N "$fn")
	#printf "Detected lexer of %q as %q\n" "$fn" "$lexer" 1>&2

	if [[ "$lexer" == text ]]
	then
		expand "$fn" | pygmentize-term -g
	else
		expand "$fn" | pygmentize-term -l "$lexer"
	fi
}

# Use colordiff (instead of diff --color=always) solely because it
# produces consistent formatting on a per-line basis, saving us from
# having to keep track of state across lines
diff=(colordiff --color=yes)

for arg in "$@"
do
	if [[ $arg == -* ]]
	then
		diff+=("$arg")
	else
		exec {fd}< <(prepare "$arg")
		diff+=(/dev/fd/$fd)
	fi
done

sed=(
	sed
	# replace "all attributes off" (used by Pygmentize to turn off
	# bold) with "normal intensity"
	-e 's/\x1b\[00m/\x1b[22m/g'

	# Replace colordiff's foreground colors with some dark background
	# colors. You can customize them here.
	-e 's/^\x1b\[0;36m/\x1b[0;36;48;5;23m/' # cyan
	-e 's/^\x1b\[0;31m/\x1b[0;31;48;5;52m/' # red
	-e 's/^\x1b\[0;32m/\x1b[0;32;48;5;22m/' # green

	# Extend background color across the entire terminal window width
	-e 's/\x1b\[0;0m$/\x1b\[K\x1b[0m/'
)

"${diff[@]}" | "${sed[@]}"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment