diff --git a/roottest/scripts/custom_diff.py b/roottest/scripts/custom_diff.py index b08ba45fefc23..10f5e1e9c97bf 100755 --- a/roottest/scripts/custom_diff.py +++ b/roottest/scripts/custom_diff.py @@ -1,61 +1,85 @@ -""" Clever (with filters) compare command using difflib.py providing diffs in four formats: - - * ndiff: lists every line and highlights interline changes. - * context: highlights clusters of changes in a before/after format. - * unified: highlights clusters of changes in an inline format. - * html: generates side by side comparison with change highlights. - - """ -import sys, os, time, difflib, optparse, re +"""Clever (with filters) compare command using difflib.py providing diffs in four formats: + +* ndiff: lists every line and highlights interline changes. +* context: highlights clusters of changes in a before/after format. +* unified: highlights clusters of changes in an inline format. +* html: generates side by side comparison with change highlights. + +""" +import difflib +import optparse +import os +import re +import sys +import time + + #--------------------------------------------------------------------------------------------------------------------------- #---Filter and substitutions------------------------------------------------------------------------------------------------ +class LineFilter: + """A line filter to suppress lines in the diff. + self.skip_regexes contains patterns to skip entirely. + self.substitutions contains patterns to replace with the given strings. + These patterns only need to be compiled once for all the incoming lines. + """ + + def __init__(self): + # Skip these lines + self.skip_regexes = [ + re.compile(pattern) + for pattern in [ + r"^Processing ", # Interpreted macros + r"^Info in <\w+::ACLiC>: creating shared library", # Compiled macros + r"^In file included from input_line", # Wrapper input line + r"^[:space:]*$", # Lines which are empty apart from spaces + ] + ] + + # Replace these patterns in all lines + self.substitutions = [ + (re.compile(r"[ ]@0x[a-fA-F0-9]+"), ""), # Remove pointers from output + (re.compile(r"std::__[0-9]+::"), "std::"), # Canonicalise standard namespaces + ( + re.compile(r"^(\S*/|)([^:/]+)[-:0-9]*(?=: error:)"), + r"\2", + ), # Trim file paths and line numbers from lines with ": error:" + (re.compile(r"(input_line_|ROOT_prompt_)[0-9]+"), r"\1--"), # Canonicalise input_line_123, ROOT_prompt_345 + ] + + def filter(self, lines, ignoreWhiteSpace=False): + outlines = [] + for line in lines: + if sys.platform == "win32": + if "Creating library " in line: + continue + if "_ACLiC_dict" in line: + continue + if "Warning in :" in line: + continue + if "Warning in :" in line: + continue + if "Error: Removing " in line: + continue + if " -nologo -TP -c -nologo -I" in line: + continue + if "rootcling -v1 -f " in line: + continue + if "No precompiled header available" in line: + continue + + # ---Skip all lines matching predefined expressions--------------------------- + if any(pattern.match(line) is not None for pattern in self.skip_regexes): + continue -def filter(lines, ignoreWhiteSpace = False): - outlines = [] - for line in lines: - if sys.platform == 'win32': - if 'Creating library ' in line: - continue - if '_ACLiC_dict' in line: - continue - if 'Warning in :' in line: - continue - if 'Warning in :' in line: - continue - if 'Error: Removing ' in line: - continue - if ' -nologo -TP -c -nologo -I' in line: - continue - if 'rootcling -v1 -f ' in line: - continue - if 'No precompiled header available' in line: - continue - #if line in ['\n', '\r\n']: - # continue - #---Processing line from interpreter (root.exe)------------------------------ - if re.match(r'^Processing ', line): - continue - #---ACLiC info--------------------------------------------------------------- - if re.match(r'^Info in <\w+::ACLiC>: creating shared library', line): - continue - #---Compilation error-------------------------------------------------------- - elif re.search(r': error:', line): - nline = re.sub(r'\S+/', '', line) - nline = re.sub(r'(:|_)[0-9]+(?=:)', ':--', nline) - #---Wrapper input line------------------------------------------------------- - elif re.match(r'^In file included from input_line', line): - continue - else: - nline = line - #---Remove Addresses in cling/cint------------------------------------------- - nline = re.sub(r'[ ]@0x[a-fA-F0-9]+', '', nline) - #---Remove versioning in std------------------------------------------------- - nline = re.sub(r'std::__[0-9]::', 'std::', nline) - #---Remove white spaces------------------------------------------------------ - if (ignoreWhiteSpace): - nline = re.sub(r'[ ]', '', nline) - outlines.append(nline) - return outlines + # ---Apply predefined replacements-------------------------------------------- + for pattern, replacement in self.substitutions: + line = pattern.sub(replacement, line) + + # ---Remove white spaces------------------------------------------------------ + if ignoreWhiteSpace: + line = re.sub(r"[ ]", "", line) + outlines.append(line) + return outlines #----------------------------------------------------------------------------------------------------------------------------- def main(): @@ -67,16 +91,16 @@ def main(): parser.add_option("-n", action="store_true", default=False, help='Produce a ndiff format diff') parser.add_option("-l", "--lines", type="int", default=3, help='Set number of context lines (default 3)') (options, args) = parser.parse_args() - + if len(args) == 0: parser.print_help() sys.exit(1) if len(args) != 2: parser.error("need to specify both a fromfile and tofile") - + n = options.lines fromfile, tofile = args - + fromdate = time.ctime(os.stat(fromfile).st_mtime) todate = time.ctime(os.stat(tofile).st_mtime) if sys.platform == 'win32': @@ -86,17 +110,18 @@ def main(): fromlines = open(fromfile, 'r' if sys.version_info >= (3, 4) else 'U').readlines() tolines = open(tofile, 'r' if sys.version_info >= (3, 4) else 'U').readlines() - nows_fromlines = filter(fromlines, True) - nows_tolines = filter(tolines, True) + lineFilter = LineFilter() + nows_fromlines = lineFilter.filter(fromlines, True) + nows_tolines = lineFilter.filter(tolines, True) check = difflib.context_diff(nows_fromlines, nows_tolines) try: - first = next(check) + _ = next(check) except StopIteration: sys.exit(0) - fromlines = filter(fromlines, False) - tolines = filter(tolines, False) + fromlines = lineFilter.filter(fromlines, False) + tolines = lineFilter.filter(tolines, False) if options.u: diff = difflib.unified_diff(fromlines, tolines, fromfile, tofile, fromdate, todate, n=n) @@ -106,12 +131,14 @@ def main(): diff = difflib.HtmlDiff().make_file(fromlines,tolines,fromfile,tofile,context=options.c,numlines=n) else: diff = difflib.context_diff(fromlines, tolines, fromfile, tofile, fromdate, todate, n=n) - + difflines = [line for line in diff] sys.stdout.writelines(difflines) - if difflines : sys.exit(1) - else : sys.exit(0) + if difflines: + sys.exit(1) + else: + sys.exit(0) if __name__ == '__main__': main()