Source code for bugzoo.core.fileline

# -*- coding: utf-8 -*-
__all__ = ('FileLine', 'FileLineSet', 'FileLineMap')

import typing
import collections.abc
from typing import (Dict, List, Set, Iterator, Iterable, Any, FrozenSet,
                    Callable, Optional, Tuple, TypeVar, MutableMapping,
                    Mapping)

from deprecated import deprecated
import attr

T = TypeVar('T')


[docs]@attr.s(frozen=True, slots=True) class FileLine: """Represents a one-indexed line within a specific file.""" filename = attr.ib(type=str) num = attr.ib(type=int) @staticmethod def from_string(s: str) -> 'FileLine': fn, _, s_num = s.rpartition(':') num = int(s_num) return FileLine(fn, num) @staticmethod @deprecated(version='2.1.29', reason='Replaced by FileLineMap.') def compactify(d: Dict['FileLine', Any]) -> Dict[str, Dict[int, Any]]: """ Converts a dictionary that is indexed by FileLine objects into a nested dictionary structures that maps from file names to a dictionary of line numbers. """ assert isinstance(d, dict) assert all(isinstance(x, FileLine) for x in d) out = {} # type: Dict[str, Dict[int, Any]] for (line, val) in d.items(): if not line.filename in out: out[line.filename] = {} out[line.filename][line.num] = val return out @staticmethod @deprecated(version='2.1.29', reason='Replaced by FileLineMap.') def decompactify(d: Dict[str, Dict[int, Any]]) -> 'Dict[FileLine, Any]': lines = {} # type: Dict['FileLine', Any] for fn in d: for num in d[fn]: lines[FileLine(fn, num)] = d[fn][num] return lines def __str__(self) -> str: return "{}:{}".format(self.filename, self.num)
# see: https://github.com/python/mypy/issues/5446 if typing.TYPE_CHECKING: BaseSet = Set[FileLine] BaseMap = MutableMapping[FileLine, T] else: BaseSet = collections.abc.Set BaseMap = collections.abc.MutableMapping class FileLineMap(BaseMap): """ An efficient implementation of maps indexed by file lines. Note that operations on instances of this class are NOT thread safe. """ def __init__(self, contents: Mapping[FileLine, T]) -> None: self.__contents = {} # type: Dict[str, Dict[int, T]] self.__length = 0 for line, val in contents.items(): self[line] = val def __iter__(self) -> Iterator[FileLine]: for fn in self.__contents: for lineno in self.__contents[fn]: yield FileLine(fn, lineno) def __len__(self) -> int: return self.__length def __getitem__(self, line: FileLine) -> T: return self.__contents[line.filename][line.num] def __setitem__(self, line: FileLine, val: T) -> None: if line.filename not in self.__contents: self.__contents[line.filename] = {} if line.num not in self.__contents[line.filename]: self.__length += 1 self.__contents[line.filename][line.num] = val def __delitem__(self, line: FileLine) -> None: del self.__contents[line.filename][line.num] if not self.__contents[line.filename]: del self.__contents[line.filename] self.__length -= 1
[docs]class FileLineSet(BaseSet): """A set of file lines.""" @staticmethod def from_dict(d: Dict[str, List[int]]) -> 'FileLineSet': contents = {fn: set(lines) for (fn, lines) in d.items()} return FileLineSet(contents) @staticmethod def from_list(lst: List[FileLine]) -> 'FileLineSet': """Converts a list of file lines into a FileLineSet.""" return FileLineSet.from_iter(lst) @staticmethod def from_iter(itr: Iterable[FileLine]) -> 'FileLineSet': d = {} # type: Dict[str, Set[int]] for line in itr: if not line.filename in d: d[line.filename] = set() d[line.filename].add(line.num) return FileLineSet(d) def __init__(self, contents: Optional[Dict[str, Set[int]]] = None ) -> None: if contents is None: contents = {} self.__contents = \ {fn: frozenset(line_nums) for (fn, line_nums) in contents.items()} # type: Dict[str, FrozenSet[int]]
[docs] def __iter__(self) -> Iterator[FileLine]: """Returns an iterator over the lines contained in this set.""" for fn in self.__contents: for num in self.__contents[fn]: yield FileLine(fn, num)
def __repr__(self) -> str: output = [] for (fn, set_lines) in self.__contents.items(): lines = sorted(set_lines) if lines == []: continue ranges = [[lines[0], lines[0]]] for num in lines[1:]: if num == ranges[-1][1] + 1: ranges[-1][1] = num else: ranges.append([num, num]) range_strs = [] for (start, stop) in ranges: if start == stop: range_strs.append(str(start)) else: range_strs.append("{}..{}".format(start, stop)) output.append("{}: {}".format(fn, '; '.join(range_strs))) return '\n'.join(output) def __len__(self) -> int: """Returns a count of the number of file lines in the set.""" return sum(len(lines) for lines in self.__contents.values()) def __getitem__(self, fn: str) -> Iterator[FileLine]: """ Returns an iterator over all lines contained in this set that belong to a given file. """ if not fn in self.__contents: raise StopIteration for num in self.__contents[fn]: yield FileLine(fn, num)
[docs] def __contains__(self, elem: object) -> bool: """Determines whether this set contains a given element.""" if not isinstance(elem, FileLine): return False return elem.filename in self.__contents and \ elem.num in self.__contents[elem.filename]
def filter(self, predicate: Callable[[FileLine], 'FileLineSet'] ) -> 'FileLineSet': """ Returns a subset of the file lines within this set that satisfy a given filtering criterion. """ filtered = [fileline for fileline in self if predicate(fileline)] return FileLineSet.from_list(filtered) def union(self, *others: Iterable[FileLine]) -> Set[FileLine]: """ Returns a set that contains the union of the file lines contained within this set and the given collections of file lines. """ sources = (self,) # type: Tuple[Iterable[FileLine], ...] sources = sources + others return FileLineSet.from_iter(l for src in sources for l in src) def intersection(self, *others: Iterable[FileLine]) -> Set[FileLine]: """ Returns a set of file lines that contains the intersection of the lines within this set and a given set. """ lines = set(self) for src in others: lines &= set(src) return FileLineSet.from_iter(lines) def restricted_to_files(self, filenames: List[str]) -> 'FileLineSet': """ Returns a variant of this set that only contains lines that occur in any one of the given files. (I.e., returns the intersection of this set and the set of all lines from a given set of files.) """ restricted = {} # type: Dict[str, Set[int]] for fn in filenames: if fn in self.__contents: restricted[fn] = set(self.__contents[fn]) return FileLineSet(restricted) @property def files(self) -> List[str]: """ A list of the names of the files that are represented by the file lines in this set. """ return list(self.__contents.keys()) def to_dict(self) -> Dict[str, List[int]]: """ Returns the contents of this set as a JSON/YAML-ready dictionary. """ return {fn: list(lines) for (fn, lines) in self.__contents.items()}