pytracediff: implement pager ('less') invocation internally

In order to get rid of the ntracediff.sh wrapper script, implement
invocation of 'less' internally, if the stdout is determined to
be a tty. Otherwise just print out normally.

Signed-off-by: Matti Hamalainen <ccr@tnsp.org>
Acked-by: Mike Blumenkrantz <michael.blumenkrantz@gmail.com>
Reviewed-by: Dylan Baker <dylan@pnwbakers.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/17135>
This commit is contained in:
Matti Hamalainen 2022-06-20 14:20:09 +03:00 committed by Matti Hämäläinen
parent 95fc0e1b7c
commit aab5d176b8
1 changed files with 127 additions and 93 deletions

View File

@ -26,12 +26,14 @@
########################################################################## ##########################################################################
from parse import * from parse import *
import os
import sys import sys
import re import re
import signal import signal
import functools import functools
import argparse import argparse
import difflib import difflib
import subprocess
assert sys.version_info >= (3, 6), 'Python >= 3.6 required' assert sys.version_info >= (3, 6), 'Python >= 3.6 required'
@ -54,6 +56,8 @@ PKK_ANSI_ITALIC = '3m'
### ###
def pkk_fatal(msg): def pkk_fatal(msg):
print(f"ERROR: {msg}", file=sys.stderr) print(f"ERROR: {msg}", file=sys.stderr)
if outpipe is not None:
outpipe.terminate()
sys.exit(1) sys.exit(1)
@ -61,8 +65,17 @@ def pkk_info(msg):
print(msg, file=sys.stderr) print(msg, file=sys.stderr)
def pkk_output(outpipe, msg):
if outpipe is not None:
print(msg, file=outpipe.stdin)
else:
print(msg)
def pkk_signal_handler(signal, frame): def pkk_signal_handler(signal, frame):
print("\nQuitting due to SIGINT / Ctrl+C!") print("\nQuitting due to SIGINT / Ctrl+C!")
if outpipe is not None:
outpipe.terminate()
sys.exit(1) sys.exit(1)
@ -259,6 +272,16 @@ def pkk_format_line(line, indent, width):
### Main program starts ### Main program starts
### ###
if __name__ == "__main__": if __name__ == "__main__":
### Check if output is a terminal
outpipe = None
redirect = False
try:
defwidth = os.get_terminal_size().columns
redirect = True
except OSError:
defwidth = 80
signal.signal(signal.SIGINT, pkk_signal_handler) signal.signal(signal.SIGINT, pkk_signal_handler)
### Parse arguments ### Parse arguments
@ -307,7 +330,7 @@ if __name__ == "__main__":
optparser.add_argument("-w", "--width", optparser.add_argument("-w", "--width",
dest="output_width", dest="output_width",
type=functools.partial(pkk_arg_range, vmin=16, vmax=512), default=80, type=functools.partial(pkk_arg_range, vmin=16, vmax=512), default=defwidth,
metavar="N", metavar="N",
help="output width (default: %(default)s)") help="output width (default: %(default)s)")
@ -327,115 +350,126 @@ if __name__ == "__main__":
print("The files are identical.") print("The files are identical.")
sys.exit(0) sys.exit(0)
### Output results ### Redirect output to 'less' if stdout is a tty
pkk_info("Outputting diff ...") try:
colwidth = int((options.output_width - 3) / 2) if redirect:
colfmt = "{}{:"+ str(colwidth) +"s}{} {}{}{} {}{:"+ str(colwidth) +"s}{}" outpipe = subprocess.Popen(["less", "-S", "-R"], stdin=subprocess.PIPE, encoding="utf8")
printer = PKKPrettyPrinter(options) ### Output results
pkk_info("Outputting diff ...")
colwidth = int((options.output_width - 3) / 2)
colfmt = "{}{:"+ str(colwidth) +"s}{} {}{}{} {}{:"+ str(colwidth) +"s}{}"
prevtag = "" printer = PKKPrettyPrinter(options)
for tag, start1, end1, start2, end2 in opcodes:
if tag == "equal":
show_args = False
if options.suppress_common:
if tag != prevtag:
print("[...]")
continue
sep = "|" prevtag = ""
ansi1 = ansi2 = ansiend = "" for tag, start1, end1, start2, end2 in opcodes:
show_args = False if tag == "equal":
elif tag == "insert": show_args = False
sep = "+" if options.suppress_common:
ansi1 = "" if tag != prevtag:
ansi2 = PKK_ANSI_ESC + PKK_ANSI_GREEN pkk_output(outpipe, "[...]")
show_args = True continue
elif tag == "delete":
sep = "-"
ansi1 = PKK_ANSI_ESC + PKK_ANSI_RED
ansi2 = ""
show_args = True
elif tag == "replace":
sep = ">"
ansi1 = ansi2 = PKK_ANSI_ESC + PKK_ANSI_BOLD
show_args = True
else:
pkk_fatal(f"Internal error, unsupported difflib.SequenceMatcher operation '{tag}'.")
# No ANSI, please sep = "|"
if options.plain: ansi1 = ansi2 = ansiend = ""
ansi1 = ansisep = ansi2 = ansiend = "" show_args = False
else: elif tag == "insert":
ansisep = PKK_ANSI_ESC + PKK_ANSI_PURPLE sep = "+"
ansiend = PKK_ANSI_ESC + PKK_ANSI_NORMAL ansi1 = ""
ansi2 = PKK_ANSI_ESC + PKK_ANSI_GREEN
show_args = True
elif tag == "delete":
sep = "-"
ansi1 = PKK_ANSI_ESC + PKK_ANSI_RED
ansi2 = ""
show_args = True
elif tag == "replace":
sep = ">"
ansi1 = ansi2 = PKK_ANSI_ESC + PKK_ANSI_BOLD
show_args = True
else:
pkk_fatal(f"Internal error, unsupported difflib.SequenceMatcher operation '{tag}'.")
# No ANSI, please
if options.plain:
ansi1 = ansisep = ansi2 = ansiend = ""
else:
ansisep = PKK_ANSI_ESC + PKK_ANSI_PURPLE
ansiend = PKK_ANSI_ESC + PKK_ANSI_NORMAL
# Print out the block # Print out the block
ncall1 = start1 ncall1 = start1
ncall2 = start2 ncall2 = start2
last1 = last2 = False last1 = last2 = False
while True: while True:
# Get line data # Get line data
if ncall1 < end1: if ncall1 < end1:
if not options.ignore_junk or not stack1[ncall1].is_junk: if not options.ignore_junk or not stack1[ncall1].is_junk:
printer.entry_start(show_args) printer.entry_start(show_args)
stack1[ncall1].visit(printer) stack1[ncall1].visit(printer)
data1 = printer.entry_get() data1 = printer.entry_get()
else:
data1 = []
ncall1 += 1
else: else:
data1 = [] data1 = []
ncall1 += 1 last1 = True
else:
data1 = []
last1 = True
if ncall2 < end2: if ncall2 < end2:
if not options.ignore_junk or not stack2[ncall2].is_junk: if not options.ignore_junk or not stack2[ncall2].is_junk:
printer.entry_start(show_args) printer.entry_start(show_args)
stack2[ncall2].visit(printer) stack2[ncall2].visit(printer)
data2 = printer.entry_get() data2 = printer.entry_get()
else:
data2 = []
ncall2 += 1
else: else:
data2 = [] data2 = []
ncall2 += 1 last2 = True
else:
data2 = []
last2 = True
# Check if we are at last call of both # Check if we are at last call of both
if last1 and last2: if last1 and last2:
break break
nline = 0 nline = 0
while nline < len(data1) or nline < len(data2): while nline < len(data1) or nline < len(data2):
# Determine line start indentation # Determine line start indentation
if nline > 0: if nline > 0:
if options.suppress_variants: if options.suppress_variants:
indent = " "*8 indent = " "*8
else:
indent = " "*12
else: else:
indent = " "*12 indent = ""
else:
indent = ""
line1 = pkk_get_line(data1, nline) line1 = pkk_get_line(data1, nline)
line2 = pkk_get_line(data2, nline) line2 = pkk_get_line(data2, nline)
# Highlight differing lines if not plain # Highlight differing lines if not plain
if not options.plain and line1 != line2: if not options.plain and line1 != line2:
if tag == "insert" or tag == "delete": if tag == "insert" or tag == "delete":
ansi1 = ansi1 + PKK_ANSI_ESC + PKK_ANSI_BOLD ansi1 = ansi1 + PKK_ANSI_ESC + PKK_ANSI_BOLD
elif tag == "replace": elif tag == "replace":
ansi1 = ansi2 = ansi1 + PKK_ANSI_ESC + PKK_ANSI_YELLOW ansi1 = ansi2 = ansi1 + PKK_ANSI_ESC + PKK_ANSI_YELLOW
# Output line # Output line
print(colfmt.format( pkk_output(outpipe, colfmt.format(
ansi1, pkk_format_line(line1, indent, colwidth), ansiend, ansi1, pkk_format_line(line1, indent, colwidth), ansiend,
ansisep, sep, ansiend, ansisep, sep, ansiend,
ansi2, pkk_format_line(line2, indent, colwidth), ansiend). ansi2, pkk_format_line(line2, indent, colwidth), ansiend).
rstrip()) rstrip())
nline += 1 nline += 1
if tag == "equal" and options.suppress_common: if tag == "equal" and options.suppress_common:
print("[...]") pkk_output(outpipe, "[...]")
prevtag = tag prevtag = tag
except Exception as e:
pkk_fatal(str(e))
if outpipe is not None:
outpipe.communicate()