Failed Conditions
Pull Request — master (#2076)
by Abdeali
02:11
created

Diff.delete_line()   A

Complexity

Conditions 1

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 1
dl 0
loc 7
rs 9.4285
1
import copy
2
import difflib
3
4
from coalib.results.LineDiff import LineDiff, ConflictError
5
from coalib.results.SourceRange import SourceRange
6
7
8
class Diff:
9
    """
10
    A Diff result represents a difference for one file.
11
    """
12
13
    def __init__(self, file_list):
14
        """
15
        Creates an empty diff for the given file.
16
17
        :param file_list: The original (unmodified) file as a list of its
18
                          lines.
19
        """
20
        self._changes = {}
21
        self._file = file_list
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable file_list does not seem to be defined.
Loading history...
22
23
    @classmethod
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable classmethod does not seem to be defined.
Loading history...
24
    def from_string_arrays(cls, file_array_1, file_array_2):
25
        """
26
        Creates a Diff object from two arrays containing strings.
27
28
        If this Diff is applied to the original array, the second array will be
29
        created.
30
31
        :param file_array_1: Original array
32
        :param file_array_2: Array to compare
33
        """
34
        result = cls(file_array_1)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable file_array_1 does not seem to be defined.
Loading history...
35
36
        matcher = difflib.SequenceMatcher(None, file_array_1, file_array_2)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable file_array_2 does not seem to be defined.
Loading history...
37
        # We use this because its faster (generator) and doesnt yield as much
38
        # useless information as get_opcodes.
39
        for change_group in matcher.get_grouped_opcodes(1):
40
            for (tag,
41
                 a_index_1,
42
                 a_index_2,
43
                 b_index_1,
44
                 b_index_2) in change_group:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable change_group does not seem to be defined.
Loading history...
45
                if tag == "delete":
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable tag does not seem to be defined.
Loading history...
46
                    for index in range(a_index_1+1, a_index_2+1):
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable a_index_2 does not seem to be defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable a_index_1 does not seem to be defined.
Loading history...
47
                        result.delete_line(index)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable index does not seem to be defined.
Loading history...
48
                elif tag == "insert":
49
                    # We add after line, they add before, so dont add 1 here
50
                    result.add_lines(a_index_1,
51
                                     file_array_2[b_index_1:b_index_2])
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable b_index_2 does not seem to be defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable b_index_1 does not seem to be defined.
Loading history...
52
                elif tag == "replace":
53
                    result.change_line(a_index_1+1,
54
                                       file_array_1[a_index_1],
55
                                       file_array_2[b_index_1])
56
                    result.add_lines(a_index_1+1,
57
                                     file_array_2[b_index_1+1:b_index_2])
58
                    for index in range(a_index_1+2, a_index_2+1):
59
                        result.delete_line(index)
60
61
        return result
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable result does not seem to be defined.
Loading history...
62
63
    @classmethod
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable classmethod does not seem to be defined.
Loading history...
64
    def from_clang_fixit(cls, fixit, file):
65
        """
66
        Creates a Diff object from a given clang fixit and the file contents.
67
68
        :param fixit: A cindex.Fixit object.
69
        :param file:  A list of lines in the file to apply the fixit to.
70
        :return:      The corresponding Diff object.
71
        """
72
        assert isinstance(file, (list, tuple))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable list does not seem to be defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable file does not seem to be defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable tuple does not seem to be defined.
Loading history...
73
74
        oldvalue = '\n'.join(file[fixit.range.start.line-1:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable fixit does not seem to be defined.
Loading history...
75
                                  fixit.range.end.line])
76
        endindex = fixit.range.end.column - len(file[fixit.range.end.line-1])-1
77
78
        newvalue = (oldvalue[:fixit.range.start.column-1] +
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable oldvalue does not seem to be defined.
Loading history...
79
                    fixit.value +
80
                    oldvalue[endindex:])
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable endindex does not seem to be defined.
Loading history...
81
        new_file = (file[:fixit.range.start.line-1] +
82
                    type(file)(newvalue.splitlines(True)) +
83
                    file[fixit.range.end.line:])
84
85
        return cls.from_string_arrays(file, new_file)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable new_file does not seem to be defined.
Loading history...
86
87
    def _get_change(self, line_nr, min_line=1):
88
        if not isinstance(line_nr, int):
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable int does not seem to be defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable line_nr does not seem to be defined.
Loading history...
89
            raise TypeError("line_nr needs to be an integer.")
90
        if line_nr < min_line:
91
            raise ValueError("The given line number is not allowed.")
92
93
        return self._changes.get(line_nr, LineDiff())
94
95
    def __len__(self):
96
        return len(self._changes)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable self does not seem to be defined.
Loading history...
97
98
    @property
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable property does not seem to be defined.
Loading history...
99
    def original(self):
100
        """
101
        Retrieves the original file.
102
        """
103
        return self._file
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable self does not seem to be defined.
Loading history...
104
105
    @property
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable property does not seem to be defined.
Loading history...
106
    def modified(self):
107
        """
108
        Calculates the modified file, after applying the Diff to the original.
109
        """
110
        result = []
111
        current_line = 0
112
113
        # Note that line_nr counts from _1_ although 0 is possible when
114
        # inserting lines before everything
115
        for line_nr in sorted(self._changes):
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable self does not seem to be defined.
Loading history...
116
            result.extend(self._file[current_line:max(line_nr-1, 0)])
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable current_line does not seem to be defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable line_nr does not seem to be defined.
Loading history...
117
            linediff = self._changes[line_nr]
118
            if not linediff.delete and not linediff.change and line_nr > 0:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable linediff does not seem to be defined.
Loading history...
119
                result.append(self._file[line_nr-1])
120
            elif linediff.change:
121
                result.append(linediff.change[1])
122
123
            if linediff.add_after:
124
                result.extend(linediff.add_after)
125
126
            current_line = line_nr
127
128
        result.extend(self._file[current_line:])
129
130
        return result
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable result does not seem to be defined.
Loading history...
131
132
    @property
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable property does not seem to be defined.
Loading history...
133
    def unified_diff(self):
134
        """
135
        Generates a unified diff corresponding to this patch.
136
137
        Note that the unified diff is not deterministic and thus not suitable
138
        for equality comparison.
139
        """
140
        return ''.join(difflib.unified_diff(self.original, self.modified))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable self does not seem to be defined.
Loading history...
141
142
    def __json__(self):
143
        """
144
        Override JSON export, using the unified diff is the easiest thing for
145
        the users.
146
        """
147
        return self.unified_diff
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable self does not seem to be defined.
Loading history...
148
149
    def affected_code(self, filename):
150
        """
151
        Creates a list of SourceRange objects which point to the related code.
152
        Changes on continuous lines will be put into one SourceRange.
153
154
        :param filename: The filename to associate the SourceRange's to.
155
        :return:         A list of all related SourceRange objects.
156
        """
157
        return list(diff.range(filename) for diff in self.split_diff())
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable filename does not seem to be defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable diff does not seem to be defined.
Loading history...
158
159
    def split_diff(self):
160
        """
161
        Splits this diff into small pieces, such that several continuously
162
        altered lines are still together in one diff. All subdiffs will be
163
        yielded.
164
        """
165
        diffs = []
166
167
        last_line = -1
168
        this_diff = Diff(self._file)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable self does not seem to be defined.
Loading history...
169
        for line in sorted(self._changes.keys()):
170
            if line != last_line + 1 and len(this_diff._changes) > 0:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable line does not seem to be defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable this_diff does not seem to be defined.
Loading history...
171
                yield this_diff
172
                this_diff = Diff(self._file)
173
174
            last_line = line
175
            this_diff._changes[line] = self._changes[line]
176
177
        if len(this_diff._changes) > 0:
178
            yield this_diff
179
180
    def range(self, filename):
181
        """
182
        Calculates a SourceRange spanning over the whole Diff. If something is
183
        added after the 0th line (i.e. before the first line) the first line
184
        will be included in the SourceRange.
185
186
        :param filename: The filename to associate the SourceRange with.
187
        :return:         A SourceRange object.
188
        """
189
        start = min(self._changes.keys())
190
        end = max(self._changes.keys())
191
        return SourceRange.from_values(filename,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable filename does not seem to be defined.
Loading history...
192
                                       start_line=max(1, start),
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable start does not seem to be defined.
Loading history...
193
                                       end_line=max(1, end))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable end does not seem to be defined.
Loading history...
194
195
    def __add__(self, other):
196
        """
197
        Adds another diff to this one. Will throw an exception if this is not
198
        possible. (This will *not* be done in place.)
199
        """
200
        if not isinstance(other, Diff):
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable Diff does not seem to be defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable other does not seem to be defined.
Loading history...
201
            raise TypeError("Only diffs can be added to a diff.")
202
203
        result = copy.deepcopy(self)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable self does not seem to be defined.
Loading history...
204
205
        for line_nr in other._changes:
206
            change = other._changes[line_nr]
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable line_nr does not seem to be defined.
Loading history...
207
            if change.delete is True:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable change does not seem to be defined.
Loading history...
208
                result.delete_line(line_nr)
209
            if change.add_after is not False:
210
                result.add_lines(line_nr, change.add_after)
211
            if change.change is not False:
212
                result.change_line(line_nr, change.change[0], change.change[1])
213
214
        return result
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable result does not seem to be defined.
Loading history...
215
216
    def delete_line(self, line_nr):
217
        """
218
        Mark the given line nr as deleted. The first line is line number 1.
219
        """
220
        linediff = self._get_change(line_nr)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable line_nr does not seem to be defined.
Loading history...
221
        linediff.delete = True
222
        self._changes[line_nr] = linediff
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable linediff does not seem to be defined.
Loading history...
223
224
    def add_lines(self, line_nr_before, lines):
225
        """
226
        Adds lines after the given line number.
227
228
        :param line_nr_before: Line number of the line before the additions.
229
                               Use 0 for insert lines before everything.
230
        :param lines:          A list of lines to add.
231
        """
232
        if lines == []:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable lines does not seem to be defined.
Loading history...
233
            return  # No action
234
235
        linediff = self._get_change(line_nr_before, min_line=0)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable line_nr_before does not seem to be defined.
Loading history...
236
        if linediff.add_after is not False:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable linediff does not seem to be defined.
Loading history...
237
            raise ConflictError("Cannot add lines after the given line since "
238
                                "there are already lines.")
239
240
        linediff.add_after = lines
241
        self._changes[line_nr_before] = linediff
242
243
    def change_line(self, line_nr, original_line, replacement):
244
        """
245
        Changes the given line with the given line number. The replacement will
246
        be there instead.
247
        """
248
        linediff = self._get_change(line_nr)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable line_nr does not seem to be defined.
Loading history...
249
        if linediff.change is not False:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable linediff does not seem to be defined.
Loading history...
250
            raise ConflictError("An already changed line cannot be changed.")
251
252
        linediff.change = (original_line, replacement)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable original_line does not seem to be defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable replacement does not seem to be defined.
Loading history...
253
        self._changes[line_nr] = linediff
254
255
    def __eq__(self, other):
256
        return ((self._file == other._file) and
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable self does not seem to be defined.
Loading history...
257
                (self.modified == other.modified))
258