Completed
Pull Request — master (#1937)
by Lasse
01:44
created

coalib.results.Result.__add__()   B

Complexity

Conditions 5

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 5
dl 0
loc 16
rs 8.5454
1
import uuid
2
from os.path import relpath
3
4
from coalib.misc.Decorators import (
5
    enforce_signature, generate_ordering, generate_repr, get_public_members)
6
from coalib.results.RESULT_SEVERITY import RESULT_SEVERITY
7
from coalib.results.SourceRange import SourceRange
8
9
10
# Omit additional info, debug message and diffs for brevity
11
@generate_repr(("id", hex),
12
               "origin",
13
               "affected_code",
14
               ("severity", RESULT_SEVERITY.reverse.get),
15
               "message")
16
@generate_ordering("affected_code",
17
                   "severity",
18
                   "origin",
19
                   "message",
20
                   "additional_info",
21
                   "debug_msg",
22
                   "diffs")
23
class Result:
24
    """
25
    A result is anything that has an origin and a message.
26
27
    Optionally it might affect a file.
28
    """
29
30
    @enforce_signature
31
    def __init__(self,
32
                 origin,
33
                 message: str,
34
                 affected_code: (tuple, list)=(),
35
                 severity: int=RESULT_SEVERITY.NORMAL,
36
                 additional_info: str="",
37
                 debug_msg="",
38
                 diffs: (dict, None)=None):
39
        """
40
        :param origin:          Class name or class of the creator of this
41
                                object.
42
        :param message:         Message to show with this result.
43
        :param affected_code:   A tuple of SourceRange objects pointing to
44
                                related positions in the source code.
45
        :param severity:        Severity of this result.
46
        :param additional_info: A long description holding additional
47
                                information about the issue and/or how to fix
48
                                it. You can use this like a manual entry for a
49
                                category of issues.
50
        :param debug_msg:       A message which may help the user find out why
51
                                this result was yielded.
52
        :param diffs:           A dictionary associating a Diff object with each
53
                                filename.
54
        """
55
        origin = origin or ""
56
        if not isinstance(origin, str):
57
            origin = origin.__class__.__name__
58
        if severity not in RESULT_SEVERITY.reverse:
59
            raise ValueError("severity is no valid RESULT_SEVERITY")
60
61
        self.origin = origin
62
        self.message = message
63
        self.debug_msg = debug_msg
64
        self.additional_info = additional_info
65
        # Sorting is important for tuple comparison
66
        self.affected_code = tuple(sorted(affected_code))
67
        self.severity = severity
68
        self.diffs = diffs
69
        self.id = uuid.uuid4().int
70
71
    @classmethod
72
    @enforce_signature
73
    def from_values(cls,
74
                    origin,
75
                    message: str,
76
                    file: str,
77
                    line: (int, None)=None,
78
                    column: (int, None)=None,
79
                    end_line: (int, None)=None,
80
                    end_column: (int, None)=None,
81
                    severity: int=RESULT_SEVERITY.NORMAL,
82
                    additional_info: str="",
83
                    debug_msg="",
84
                    diffs: (dict, None)=None):
85
        """
86
        Creates a result with only one SourceRange with the given start and end
87
        locations.
88
89
        :param origin:          Class name or class of the creator of this
90
                                object.
91
        :param message:         A message to explain the result.
92
        :param file:            The related file.
93
        :param line:            The first related line in the file.
94
                                (First line is 1)
95
        :param column:          The column indicating the first character.
96
                                (First character is 1)
97
        :param end_line:        The last related line in the file.
98
        :param end_column:      The column indicating the last character.
99
        :param severity:        A RESULT_SEVERITY object.
100
        :param debug_msg:       Another message for debugging purposes.
101
        :param additional_info: A long description holding additional
102
                                information about the issue and/or how to fix
103
                                it. You can use this like a manual entry for a
104
                                category of issues.
105
        :param diffs:           A dictionary with filenames as key and Diff
106
                                objects associated with them.
107
        """
108
        range = SourceRange.from_values(file,
109
                                        line,
110
                                        column,
111
                                        end_line,
112
                                        end_column)
113
114
        return cls(origin=origin,
115
                   message=message,
116
                   affected_code=(range,),
117
                   severity=severity,
118
                   additional_info=additional_info,
119
                   debug_msg=debug_msg,
120
                   diffs=diffs)
121
122
    def to_string_dict(self):
123
        """
124
        Makes a dictionary which has all keys and values as strings and
125
        contains all the data that the base Result has.
126
127
        FIXME: diffs are not serialized ATM.
128
        FIXME: Only the first SourceRange of affected_code is serialized. If
129
        there are more, this data is currently missing.
130
131
        :return: Dictionary with keys and values as string.
132
        """
133
        retval = {}
134
135
        members = ["id",
136
                   "additional_info",
137
                   "debug_msg",
138
                   "message",
139
                   "origin"]
140
141
        for member in members:
142
            value = getattr(self, member)
143
            retval[member] = "" if value == None else str(value)
144
145
        retval["severity"] = str(RESULT_SEVERITY.reverse.get(
146
            self.severity, ""))
147
        if len(self.affected_code) > 0:
148
            retval["file"] = self.affected_code[0].file
149
            line = self.affected_code[0].start.line
150
            retval["line_nr"] = "" if line is None else str(line)
151
        else:
152
            retval["file"], retval["line_nr"] = "", ""
153
154
        return retval
155
156
    @enforce_signature
157
    def apply(self, file_dict: dict):
158
        """
159
        Applies all contained diffs to the given file_dict. This operation will
160
        be done in-place.
161
162
        :param file_dict: A dictionary containing all files with filename as
163
                          key and all lines a value. Will be modified.
164
        """
165
        for filename in self.diffs:
166
            file_dict[filename] = self.diffs[filename].modified
167
168
    def __add__(self, other):
169
        """
170
        Joins those patches to one patch.
171
172
        :param other: The other patch.
173
        """
174
        assert isinstance(self.diffs, dict)
175
        assert isinstance(other.diffs, dict)
176
177
        for filename in other.diffs:
178
            if filename in self.diffs:
179
                self.diffs[filename] += other.diffs[filename]
180
            else:
181
                self.diffs[filename] = other.diffs[filename]
182
183
        return self
184
185
    def overlaps(self, ranges):
186
        """
187
        Determines if the result overlaps with source ranges provided.
188
189
        :param ranges: A list SourceRange objects to check for overlap.
190
        :return:       True if the ranges overlap with the result.
191
        """
192
        if isinstance(ranges, SourceRange):
193
            ranges = [ranges]
194
195
        for range in ranges:
196
            for self_range in self.affected_code:
197
                if range.overlaps(self_range):
198
                    return True
199
200
        return False
201
202
    def location_repr(self):
203
        """
204
        Retrieves a string, that briefly represents
205
        the affected code of the result.
206
207
        :return: A string containing all of the affected files
208
                 seperated by a comma.
209
        """
210
211
        if not self.affected_code:
212
            return "the whole project"
213
214
        # Set important to exclude duplicate file names
215
        range_paths = set(sourcerange.file
216
                          for sourcerange in self.affected_code)
217
218
        return ', '.join(repr(relpath(range_path))
219
                         for range_path in sorted(range_paths))
220
221
    def __json__(self, use_relpath=False):
222
        _dict = get_public_members(self)
223
        if use_relpath and _dict['diffs']:
224
            _dict['diffs'] = {relpath(file): diff
225
                              for file, diff in _dict['diffs'].items()}
226
        return _dict
227