149 lines
5.5 KiB
Python
Executable file
149 lines
5.5 KiB
Python
Executable file
#!/usr/bin/env python
|
|
|
|
from __future__ import absolute_import, division, with_statement
|
|
|
|
import fileinput
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
# TERM could be e.g. xterm-256color or screen-256color.
|
|
has_256_color = ('-256col' in os.environ.get('TERM', ''))
|
|
|
|
index_color = int(os.environ.get('DIFF_INDEX_COLOR',
|
|
has_256_color and 32 or 36))
|
|
old_mode_color = int(os.environ.get('DIFF_OLD_MODE_COLOR',
|
|
has_256_color and 88 or 31))
|
|
new_mode_color = int(os.environ.get('DIFF_NEW_MODE_COLOR',
|
|
has_256_color and 28 or 32))
|
|
removed_color = int(os.environ.get('DIFF_REMOVED_COLOR',
|
|
has_256_color and 160 or 31))
|
|
added_color = int(os.environ.get('DIFF_ADDED_COLOR',
|
|
has_256_color and 2 or 32))
|
|
hunk_start_color = int(os.environ.get('DIFF_HUNK_START_COLOR',
|
|
has_256_color and 32 or 36))
|
|
trailing_whitespace_color = int(has_256_color and 236 or 8)
|
|
|
|
|
|
RESET_FORMAT = '\033[0m'
|
|
COLOR_FORMAT_256 = '\033[38;5;%um'
|
|
COLOR_FORMAT_16 = '\033[38;%um'
|
|
BG_COLOR_FORMAT_256 = '\033[48;5;%um'
|
|
BG_COLOR_FORMAT_16 = '\033[48;%um'
|
|
COLOR_FORMAT = has_256_color and COLOR_FORMAT_256 or COLOR_FORMAT_16
|
|
BG_COLOR_FORMAT = has_256_color and BG_COLOR_FORMAT_256 or BG_COLOR_FORMAT_16
|
|
BEGIN_REVERSE_FORMAT = '\033[7m'
|
|
END_REVERSE_FORMAT = '\033[27m'
|
|
|
|
USAGE = """
|
|
Usage: diff ... | diff-colorize
|
|
or: diff-colorize < foo.diff
|
|
|
|
Reads unified or git-style diff data from standard input, colorizes it, and
|
|
writes the result to standard output.
|
|
""".strip()
|
|
|
|
# You can customize the color numbers used by setting these variables in your environment:
|
|
# * DIFF_INDEX_COLOR (lines starting with "Index: " or "diff --git ")
|
|
# * DIFF_OLD_MODE_COLOR (lines starting with "old mode"; these only appear in git-style diffs)
|
|
# * DIFF_NEW_MODE_COLOR (lines starting with "new mode"; these only appear in git-style diffs)
|
|
# * DIFF_REMOVED_COLOR (lines starting with "-")
|
|
# * DIFF_ADDED_COLOR (lines starting with "+")
|
|
# * DIFF_HUNK_START_COLOR (lines starting with "@@")
|
|
|
|
# Everything in the unified diff format is identified by a prefix. The prefixes are:
|
|
# 'Index: ': File marker (unified diff)
|
|
# 'diff --git': File marker (git-style diff)
|
|
# 'old mode': File permissions mode before change
|
|
# 'new mode': File permissions mode after change
|
|
# '---': Defining '-' (giving the name and modification date of the file before change)
|
|
# '+++': Defining '+' (giving the name and modification date of the file after change)
|
|
# '-': Line before change (i.e., removed)
|
|
# '+': Line after change (i.e., added)
|
|
# ' ': Line that hasn't changed
|
|
# '@@': Hunk start (@@ -start,length +start, length @@)
|
|
#
|
|
# We need to look for these prefixes in order, in order to handle '---'/'+++' before '-'/'+'. Hence the OrderedDict.
|
|
class OrderedDict(dict):
|
|
def __init__(self, input=None):
|
|
if input is None:
|
|
self.keys = []
|
|
super(OrderedDict, self).__init__()
|
|
elif isinstance(input, dict):
|
|
self.keys = list(input)
|
|
super(OrderedDict, self).__init__(input)
|
|
else:
|
|
self.keys = [k for k, v in input]
|
|
super(OrderedDict, self).__init__(input)
|
|
def __iter__(self):
|
|
return iter(self.keys)
|
|
def __setitem__(self, k, v):
|
|
if k not in self:
|
|
self.keys.append(k)
|
|
super(OrderedDict, self).__setitem__(k, v)
|
|
def __delitem__(self, k):
|
|
super(OrderedDict, self).__delitem__(k)
|
|
self.keys.remove(k)
|
|
|
|
# Each value includes not only the terminal-config characters, but also the key, somewhere within it (possibly between two terminal-config strings).
|
|
# Theoretically, you could replace the key with some other string or leave it out entirely, if you wanted to, but I wouldn't recommend it.
|
|
prefixes = OrderedDict()
|
|
prefixes['---'] = (
|
|
COLOR_FORMAT % (removed_color,)
|
|
+ BEGIN_REVERSE_FORMAT
|
|
+ '---'
|
|
+ END_REVERSE_FORMAT
|
|
)
|
|
prefixes['+++'] = (
|
|
COLOR_FORMAT % (added_color,)
|
|
+ BEGIN_REVERSE_FORMAT
|
|
+ '+++'
|
|
+ END_REVERSE_FORMAT
|
|
)
|
|
prefixes['-'] = (
|
|
COLOR_FORMAT % (removed_color,)
|
|
+ BEGIN_REVERSE_FORMAT
|
|
+ '-'
|
|
+ END_REVERSE_FORMAT
|
|
)
|
|
prefixes['+'] = (
|
|
COLOR_FORMAT % (added_color,)
|
|
+ BEGIN_REVERSE_FORMAT
|
|
+ '+'
|
|
+ END_REVERSE_FORMAT
|
|
)
|
|
prefixes['old mode'] = ( # Git-style diffs only
|
|
COLOR_FORMAT % (old_mode_color,)
|
|
+ BEGIN_REVERSE_FORMAT
|
|
+ 'old mode'
|
|
+ END_REVERSE_FORMAT
|
|
)
|
|
prefixes['new mode'] = ( # Git-style diffs only
|
|
COLOR_FORMAT % (new_mode_color,)
|
|
+ BEGIN_REVERSE_FORMAT
|
|
+ 'new mode'
|
|
+ END_REVERSE_FORMAT
|
|
)
|
|
prefixes['Index: '] = COLOR_FORMAT % (index_color,) + 'Index: '
|
|
prefixes['diff --git '] = COLOR_FORMAT % (index_color,) + 'diff --git '
|
|
prefixes['@@'] = (
|
|
COLOR_FORMAT % (hunk_start_color,)
|
|
# + BEGIN_REVERSE_FORMAT
|
|
+ '@@'
|
|
)
|
|
trailing_whitespace_repl = r'\1%s\2' % (BG_COLOR_FORMAT % (trailing_whitespace_color,),)
|
|
|
|
if sys.stdin.isatty():
|
|
# Standard input is a TTY, meaning that the user ran 'diff-colorize' at the shell prompt, without redirecting anything into it. Print usage info and exit.
|
|
sys.exit(USAGE)
|
|
|
|
for line in fileinput.input():
|
|
for prefix_to_test in prefixes:
|
|
if line.startswith(prefix_to_test):
|
|
sys.stdout.write(prefixes[prefix_to_test])
|
|
line = line[len(prefix_to_test):]
|
|
line = re.sub('([^ \t])([ \t]+)$', trailing_whitespace_repl, line)
|
|
|
|
sys.stdout.write(line)
|
|
|
|
sys.stdout.write(RESET_FORMAT)
|