Completed
Pull Request — master (#1625)
by Abdeali
01:38
created

coalib.results.Result.to_string_dict()   B

Complexity

Conditions 5

Size

Total Lines 32

Duplication

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