Source code for coalib.results.SourceRange

from os.path import relpath

from coala_utils.decorators import enforce_signature, get_public_members
from coalib.results.SourcePosition import SourcePosition
from coalib.results.TextRange import TextRange
from coalib.results.AbsolutePosition import AbsolutePosition


[docs]class SourceRange(TextRange): @enforce_signature def __init__(self, start: SourcePosition, end: (SourcePosition, None) = None): """ Creates a new SourceRange. :param start: A SourcePosition indicating the start of the range. :param end: A SourcePosition indicating the end of the range. If ``None`` is given, the start object will be used here. end must be in the same file and be greater than start as negative ranges are not allowed. :raises TypeError: Raised when - start is not of type SourcePosition. - end is neither of type SourcePosition, nor is it None. :raises ValueError: Raised when file of start and end mismatch. """ TextRange.__init__(self, start, end) if self.start.file != self.end.file: raise ValueError('File of start and end position do not match.')
[docs] @classmethod def from_values(cls, file, start_line=None, start_column=None, end_line=None, end_column=None): start = SourcePosition(file, start_line, start_column) if end_line or (end_column and end_column > start_column): end = SourcePosition(file, end_line if end_line else start_line, end_column) else: end = None return cls(start, end)
[docs] @classmethod @enforce_signature def from_absolute_position(cls, file: str, position_start: AbsolutePosition, position_end: (AbsolutePosition, None) = None): """ Creates a SourceRange from a start and end positions. :param file: Name of the file. :param position_start: Start of range given by AbsolutePosition. :param position_end: End of range given by AbsolutePosition or None. """ start = SourcePosition(file, position_start.line, position_start.column) end = None if position_end: end = SourcePosition(file, position_end.line, position_end.column) return cls(start, end)
@property def file(self): return self.start.file
[docs] @enforce_signature def renamed_file(self, file_diff_dict: dict): """ Retrieves the filename this source range refers to while taking the possible file renamings in the given file_diff_dict into account: :param file_diff_dict: A dictionary with filenames as key and their associated Diff objects as values. """ diff = file_diff_dict.get(self.file) if diff is None: return self.file return diff.rename if diff.rename is not False else self.file
[docs] def expand(self, file_contents): """ Passes a new SourceRange that covers the same area of a file as this one would. All values of None get replaced with absolute values. values of None will be interpreted as follows: self.start.line is None: -> 1 self.start.column is None: -> 1 self.end.line is None: -> last line of file self.end.column is None: -> last column of self.end.line :param file_contents: File contents of the applicable file :return: TextRange with absolute values """ tr = TextRange.expand(self, file_contents) return SourceRange.from_values(self.file, tr.start.line, tr.start.column, tr.end.line, tr.end.column)
[docs] @enforce_signature def affected_source(self, file_dict: dict): r""" Tells which lines are affected in a specified file within a given range. >>> from os.path import abspath >>> sr = SourceRange.from_values('file_name', start_line=2, end_line=2) >>> sr.affected_source({ ... abspath('file_name'): ('def fun():\n', ' x = 2 \n') ... }) (' x = 2 \n',) If more than one line is affected. >>> sr = SourceRange.from_values('file_name', start_line=2, end_line=3) >>> sr.affected_source({ ... abspath('file_name'): ('def fun():\n', ... ' x = 2 \n', ' print(x) \n') ... }) (' x = 2 \n', ' print(x) \n') If the file indicated at the source range is not in the `file_dict` or the lines are not given, this will return `None`: >>> sr = SourceRange.from_values('file_name_not_present', ... start_line=2, end_line=2) >>> sr.affected_source({abspath('file_name'): ... ('def fun():\n', ' x = 2 \n')}) :param file_dict: It is a dictionary where the file names are the keys and the contents of the files are the values(which is of type tuple). :return: A tuple of affected lines in the specified file. If the file is not affected or the file is not present in ``file_dict`` return ``None``. """ if self.start.file in file_dict and self.start.line and self.end.line: # line number starts from 1, index starts from 0 return file_dict[self.start.file][self.start.line - 1:self.end.line]
def __json__(self, use_relpath=False): _dict = get_public_members(self) if use_relpath: _dict['file'] = relpath(_dict['file']) return _dict def __str__(self): """ Creates a string representation of the SourceRange object. If the whole file is affected, then just the filename is shown. >>> str(SourceRange.from_values('test_file', None, None, None, None)) '...test_file' If the whole line is affected, then just the filename with starting line number and ending line number is shown. >>> str(SourceRange.from_values('test_file', 1, None, 2, None)) '...test_file: L1 : L2' This is the general case where particular column and line are specified. It shows the starting line and column and ending line and column, with filename in the beginning. >>> str(SourceRange.from_values('test_file', 1, 1, 2, 1)) '...test_file: L1 C1 : L2 C1' """ if self.start.line is None and self.end.line is None: format_str = '{0.start.file}' elif self.start.column is None and self.end.column is None: format_str = '{0.start.file}: L{0.start.line} : L{0.end.line}' else: format_str = ('{0.start.file}: L{0.start.line} C{0.start.column}' + ' : L{0.end.line} C{0.end.column}') return format_str.format(self)
[docs] def overlaps(self, other): return (self.start.file == other.start.file and super().overlaps(other))
def __contains__(self, item): return (super().__contains__(item) and self.start.file == item.start.file)