Completed
Push — master ( 42cdfe...147541 )
by
unknown
02:00
created

TextRange.expand()   B

Complexity

Conditions 5

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
dl 0
loc 24
rs 8.1671
c 0
b 0
f 0
1
import copy
2
3
from coala_utils.decorators import (
4
    enforce_signature, generate_ordering, generate_repr)
5
from coalib.results.TextPosition import TextPosition
6
7
8
@generate_repr("start", "end")
9
@generate_ordering("start", "end")
10
class TextRange:
11
12
    @enforce_signature
13
    def __init__(self, start: TextPosition, end: (TextPosition, None)=None):
14
        """
15
        Creates a new TextRange.
16
17
        :param start:       A TextPosition indicating the start of the range.
18
                            Can't be ``None``.
19
        :param end:         A TextPosition indicating the end of the range. If
20
                            ``None`` is given, the start object will be used
21
                            here.
22
        :raises TypeError:  Raised when
23
                            - start is not of type TextPosition.
24
                            - end is neither of type TextPosition, nor is it
25
                              None.
26
        :raises ValueError: Raised when end position is smaller than start
27
                            position, because negative ranges are not allowed.
28
        """
29
30
        self._start = start
31
        self._end = copy.deepcopy(start) if end is None else end
32
33
        if self._end < start:
34
            raise ValueError("End position can't be less than start position.")
35
36
    @classmethod
37
    def from_values(cls,
38
                    start_line=None,
39
                    start_column=None,
40
                    end_line=None,
41
                    end_column=None):
42
        """
43
        Creates a new TextRange.
44
45
        :param start_line:   The line number of the start position. The first
46
                             line is 1.
47
        :param start_column: The column number of the start position. The first
48
                             column is 1.
49
        :param end_line:     The line number of the end position. If this
50
                             parameter is ``None``, then the end position is set
51
                             the same like start position and end_column gets
52
                             ignored.
53
        :param end_column:   The column number of the end position.
54
        :return:             A TextRange.
55
        """
56
        start = TextPosition(start_line, start_column)
57
        if end_line is None:
58
            end = None
59
        else:
60
            end = TextPosition(end_line, end_column)
61
62
        return cls(start, end)
63
64
    @classmethod
65
    def join(cls, a, b):
66
        """
67
        Creates a new TextRange that covers the area of two overlapping ones
68
69
        :param a: TextRange (needs to overlap b)
70
        :param b: TextRange (needs to overlap a)
71
        :return:  A new TextRange covering the union of the Area of a and b
72
        """
73
        if not isinstance(a, cls) or not isinstance(b, cls):
74
            raise TypeError(
75
                "only instances of {} can be joined".format(cls.__name__))
76
77
        if not a.overlaps(b):
78
            raise ValueError(
79
                    "{}s must overlap to be joined".format(cls.__name__))
80
81
        return cls(min(a.start, b.start), max(a.end, b.end))
82
83
    @property
84
    def start(self):
85
        return self._start
86
87
    @property
88
    def end(self):
89
        return self._end
90
91
    def overlaps(self, other):
92
        return self.start <= other.end and self.end >= other.start
93
94
    def expand(self, text_lines):
95
        """
96
        Passes a new TextRange that covers the same area of a file as this one
97
        would. All values of None get replaced with absolute values.
98
99
        values of None will be interpreted as follows:
100
        self.start.line is None:   -> 1
101
        self.start.column is None: -> 1
102
        self.end.line is None:     -> last line of file
103
        self.end.column is None:   -> last column of self.end.line
104
105
        :param text_lines: File contents of the applicable file
106
        :return:           TextRange with absolute values
107
        """
108
        start_line = 1 if self.start.line is None else self.start.line
109
        start_column = 1 if self.start.column is None else self.start.column
110
        end_line = len(text_lines) if self.end.line is None else self.end.line
111
        end_column = (len(text_lines[end_line - 1]) if self.end.column is None
112
                      else self.end.column)
113
114
        return TextRange.from_values(start_line,
115
                                     start_column,
116
                                     end_line,
117
                                     end_column)
118
119
    def __contains__(self, item):
120
        return item.start >= self.start and item.end <= self.end
121