Skip to content

Instantly share code, notes, and snippets.

@methanoliver
Last active September 14, 2016 23:19
Show Gist options
  • Save methanoliver/26d59419cd28a13d62ffaa2a4a429dd2 to your computer and use it in GitHub Desktop.
Save methanoliver/26d59419cd28a13d62ffaa2a4a429dd2 to your computer and use it in GitHub Desktop.
Transmute a git diff of a file into CriticMark markup or raw HTML tags, in case your markdown processor won't accept CriticMark.
#!/usr/bin/python
from __future__ import print_function
import subprocess
import os
import re
def criticize(root, from_ref, to_ref, filename, html=False):
# Git uses "^+" for additions, "^-" for deletions, "^~$" for newlines. It's
# always deletion first additions second, so when a deletion is followed by
# addition, it's a change {~~this~>that~~}, otherwise {--deletion--} and
# {++addition++}.
# There is probably a simpler way to do this, but this one works
# passably well.
git_args = [
"git", "diff", "--unified=100000", "--word-diff=porcelain",
"--no-color",
from_ref, to_ref, filename
]
p = subprocess.Popen(git_args, cwd=root, stdout=subprocess.PIPE,
universal_newlines=True)
p.wait()
if p.returncode > 0:
raise IOError("Git complained about something.")
raw_data = p.stdout.read()
# Now we massage this into CriticMark format.
# Cut off the unified diff header.
raw_data = re.sub(
r"^diff --git .*\nindex .*\n--- .*\n\+\+\+ .*\n@@ .* @@\n", "",
raw_data, flags=re.MULTILINE)
# Replace all cases of changed pairs, i.e. - followed by a +.
raw_data = re.sub(
r"^-(?P<from>.*)\n\+(?P<to>.*)\n",
r" <del>\g<from></del><ins>\g<to></ins>\n" if html
else r" {~~\g<from>~>\g<to>~~}\n",
raw_data,
flags=re.MULTILINE
)
# Replace solo deletions and additions.
raw_data = re.sub(
r"^-(?P<del>.*)\n",
r" <del>\g<del></del>\n" if html else r" {--\g<del>--}\n",
raw_data,
flags=re.MULTILINE
)
raw_data = re.sub(
r"^\+(?P<add>.*)\n",
r" <ins>\g<add></ins>\n" if html else r" {++\g<add>++}\n",
raw_data,
flags=re.MULTILINE
)
# The rest is easier to do manually:
# If a line starts with a space, cut the space off and join it to the
# previous one.
# If a line starts with a tilde, convert it to a straight newline.
output_data = ""
for line in raw_data.split('\n'):
if line.startswith('~'):
output_data += "\n"
elif line.startswith(" "):
output_data += line[1:]
else:
output_data += line
return output_data
if __name__ == "__main__":
import argparse
import sys
def output_pattern(filename):
if any([filename.endswith(x) for x in ['.md', '.markdown']]):
return ".".join(filename.split('.')[:-1]) + ".critic.md"
return filename + ".critic.md"
parser = argparse.ArgumentParser(
description="Using git's word-based diff facility, produce a "
"diff between two versions of a given file expressed as CriticMark "
"markup.\nAssumes it's running in the root of a git tree.\n"
"Nothing will be written if the file did not change between the "
"given revisions."
)
parser.add_argument('filename', metavar='FILE', type=str, nargs='+',
help='Filenames to produce a diff for.')
parser.add_argument('--output', '-o', metavar='output_filename', type=str,
help="Output filename. Can "
"only be used when processing files one by one, "
"otherwise default pattern *.critic.md is used.")
parser.add_argument('--html', action="store_true",
help="Output HTML instead of CriticMark.")
parser.add_argument('--from', '-f', dest='from_ref', metavar='from',
type=str, required=True,
help="The 'before' version of the file, required.")
parser.add_argument('--to', '-t', dest='to_ref', metavar='to', type=str,
default='HEAD',
help="The 'after' version of the file, "
"defaults to HEAD.")
args = parser.parse_args()
if not os.path.isdir('.git'):
print("Not running in a git tree. Use -h for help.")
sys.exit(1)
if len(args.filename) > 1 and args.output:
print("Cannot write multiple output files into the same filename.")
sys.exit(1)
for filename in args.filename:
data = criticize(os.getcwd(), args.from_ref, args.to_ref,
filename, html=args.html)
if len(data):
with open(args.output or output_pattern(filename), 'w+b') as f:
f.write(data)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment