1#!/usr/bin/env python3 2 3import os 4from .unified_format_parser import UnifiedFormatParser, ChangeSet 5 6 7class ReportError(Exception): 8 pass 9 10 11class Report(object): 12 class ReportEntry: 13 def __init__(self, file_path, line_number, entry_text, line_id): 14 # type: (str, int, list, int) -> None 15 if not isinstance(line_number, int) or \ 16 not isinstance(line_id, int): 17 raise ReportError("ReportEntry constructor wrong type args") 18 self.file_path = file_path 19 self.line_number = line_number 20 self.text = entry_text 21 self.line_id = line_id 22 23 def __init__(self, report_path): 24 # type: (str) -> None 25 self.__entries = {} 26 self.__path = report_path 27 self.__last_line_order = 0 28 29 def parse(self): 30 # type: () -> None 31 raise ReportError("Please create a specialised class from 'Report'.") 32 33 def get_report_path(self): 34 # type: () -> str 35 return self.__path 36 37 def get_report_entries(self): 38 # type: () -> dict 39 return self.__entries 40 41 def add_entry(self, entry_path, entry_line_number, entry_text): 42 # type: (str, int, str) -> None 43 entry = Report.ReportEntry(entry_path, entry_line_number, entry_text, 44 self.__last_line_order) 45 if entry_path in self.__entries.keys(): 46 self.__entries[entry_path].append(entry) 47 else: 48 self.__entries[entry_path] = [entry] 49 self.__last_line_order += 1 50 51 def remove_entries(self, entry_file_path): 52 # type: (str) -> None 53 del self.__entries[entry_file_path] 54 55 def remove_entry(self, entry_path, line_id): 56 # type: (str, int) -> None 57 if entry_path in self.__entries.keys(): 58 len_entry_path = len(self.__entries[entry_path]) 59 if len_entry_path == 1: 60 del self.__entries[entry_path] 61 else: 62 if line_id in self.__entries[entry_path]: 63 self.__entries[entry_path].remove(line_id) 64 65 def patch(self, diff_obj): 66 # type: (UnifiedFormatParser) -> Report 67 filename, file_extension = os.path.splitext(self.__path) 68 patched_report = self.__class__(filename + ".patched" + file_extension) 69 remove_files = [] 70 rename_files = [] 71 remove_entry = [] 72 ChangeMode = ChangeSet.ChangeMode 73 74 # Copy entries from this report to the report we are going to patch 75 for entries in self.__entries.values(): 76 for entry in entries: 77 patched_report.add_entry(entry.file_path, entry.line_number, 78 entry.text) 79 80 # Patch the output report 81 patched_rep_entries = patched_report.get_report_entries() 82 for file_diff, change_obj in diff_obj.get_change_sets().items(): 83 if change_obj.is_change_mode(ChangeMode.COPY): 84 # Copy the original entry pointed by change_obj.orig_file into 85 # a new key in the patched report named change_obj.dst_file, 86 # that here is file_diff variable content, because this 87 # change_obj is pushed into the change_sets with the 88 # change_obj.dst_file key 89 if change_obj.orig_file in self.__entries.keys(): 90 for entry in self.__entries[change_obj.orig_file]: 91 patched_report.add_entry(file_diff, 92 entry.line_number, 93 entry.text) 94 95 if file_diff in patched_rep_entries.keys(): 96 if change_obj.is_change_mode(ChangeMode.DELETE): 97 # No need to check changes here, just remember to delete 98 # the file from the report 99 remove_files.append(file_diff) 100 continue 101 elif change_obj.is_change_mode(ChangeMode.RENAME): 102 # Remember to rename the file entry on this report 103 rename_files.append(change_obj) 104 105 for line_num, change_type in change_obj.get_change_set(): 106 len_rep = len(patched_rep_entries[file_diff]) 107 for i in range(len_rep): 108 rep_item = patched_rep_entries[file_diff][i] 109 if change_type == ChangeSet.ChangeType.REMOVE: 110 if rep_item.line_number == line_num: 111 # This line is removed with this changes, 112 # append to the list of entries to be removed 113 remove_entry.append(rep_item) 114 elif rep_item.line_number > line_num: 115 rep_item.line_number -= 1 116 elif change_type == ChangeSet.ChangeType.ADD: 117 if rep_item.line_number >= line_num: 118 rep_item.line_number += 1 119 # Remove deleted entries from the list 120 if len(remove_entry) > 0: 121 for entry in remove_entry: 122 patched_report.remove_entry(entry.file_path, 123 entry.line_id) 124 del remove_entry[:] 125 126 if len(remove_files) > 0: 127 for file_name in remove_files: 128 patched_report.remove_entries(file_name) 129 130 if len(rename_files) > 0: 131 for change_obj in rename_files: 132 patched_rep_entries[change_obj.dst_file] = \ 133 patched_rep_entries.pop(change_obj.orig_file) 134 135 return patched_report 136 137 def to_list(self): 138 # type: () -> list 139 report_list = [] 140 for _, entries in self.__entries.items(): 141 for entry in entries: 142 report_list.append(entry) 143 144 report_list.sort(key=lambda x: x.line_id) 145 return report_list 146 147 def __str__(self): 148 # type: () -> str 149 ret = "" 150 for entry in self.to_list(): 151 ret += entry.file_path + ":" + entry.line_number + ":" + entry.text 152 153 return ret 154 155 def __len__(self): 156 # type: () -> int 157 return len(self.to_list()) 158 159 def __sub__(self, report_b): 160 # type: (Report) -> Report 161 if self.__class__ != report_b.__class__: 162 raise ReportError("Diff of different type of report!") 163 164 filename, file_extension = os.path.splitext(self.__path) 165 diff_report = self.__class__(filename + ".diff" + file_extension) 166 # Put in the diff report only records of this report that are not 167 # present in the report_b. 168 for file_path, entries in self.__entries.items(): 169 rep_b_entries = report_b.get_report_entries() 170 if file_path in rep_b_entries.keys(): 171 # File path exists in report_b, so check what entries of that 172 # file path doesn't exist in report_b and add them to the diff 173 rep_b_entries_num = [ 174 x.line_number for x in rep_b_entries[file_path] 175 ] 176 for entry in entries: 177 if entry.line_number not in rep_b_entries_num: 178 diff_report.add_entry(file_path, entry.line_number, 179 entry.text) 180 else: 181 # File path doesn't exist in report_b, so add every entry 182 # of that file path to the diff 183 for entry in entries: 184 diff_report.add_entry(file_path, entry.line_number, 185 entry.text) 186 187 return diff_report 188