Source code for pyexlatex.table.models.labels.collection

from typing import Union, Iterable, List
import re
from copy import deepcopy

from pyexlatex.logic.tools import _add_if_not_none
from pyexlatex.table.models.labels.label import Label
from pyexlatex.table.models.mixins.addvalues.row import RowAddMixin
from pyexlatex.table.models.table.rowbase import RowBase


[docs]class LabelCollection(RowBase): """ Represents one row of labels. Use to construct a LabelTable to apply to a DataTable. Main usage is LabelCollection.from_str_list """ repr_cols = ['values', 'underlines']
[docs] def __init__(self, values: List[Label], underline: Union[int, str]=None): """ :param values: :param underline: int, str, or None, pass index or label to underline, pass a str range, e.g. '2-5' for a range of labels to underline, pass space separated indices as a str, e.g. '0 2' for separated underlines, or a combination, e.g. '2-5 8' """ if isinstance(values, tuple): values = list(values) self.values: List[Label] = values underline_label_indices = _convert_underline_to_label_index_list(underline) self.underlines = self._convert_label_indices_to_column_indices(underline_label_indices)
def __str__(self): return str(sum(self.values))
[docs] def matches(self, other): """ Compare on the basis of having same values, rather than same instance Use regular equality to test if same instance :param other: :return: """ for i, value in enumerate(self): try: other[i] except IndexError: return False # any one misalignment, no match if value != other[i]: return False # same number of rows, all rows equal return True
[docs] def is_subset_of(self, other) -> bool: """ Checks whether this label collection is part of another label collection. E.g. if other has ['a', 'b'] and this has ['a'] then this is a subset. """ for i, value in enumerate(other): try: matched = value == self[i] if not matched: return False except IndexError: break return True
[docs] @classmethod def from_str_list(cls, str_list: List[str], underline: Union[int, str]=None) -> 'LabelCollection': """ Args: str_list: underline: int, str, or None, pass index or label to underline, pass a str range, e.g. '2-5' for a range of labels to underline, pass space separated indices as a str, e.g. '0 2' for separated underlines, or a combination, e.g. '2-5 8' Returns: """ labels = [Label(value) for value in str_list] return cls(labels, underline=underline)
[docs] @classmethod def parse_unknown_type(cls, unknown_type: Union[str, Iterable[str], 'LabelCollection']) -> 'LabelCollection': if isinstance(unknown_type, LabelCollection): return unknown_type if isinstance(unknown_type, str): unknown_type = [unknown_type] if isinstance(unknown_type, list): return LabelCollection.from_str_list(unknown_type) else: raise ValueError(f'unable to parse type {type(unknown_type)} into label collection')
def _convert_label_indices_to_column_indices(self, label_indices: List[int]): column_indices: List[int] = [] position = 0 for i, value in enumerate(self.values): begin_position = position position += _get_item_length(value) # if this label is included in label indices, add all ints in the range of span of label if i in label_indices: column_indices += [i for i in range(begin_position, position)] if column_indices == []: return None return column_indices
[docs] def shift_underlines(self, shift: int): if self.underlines is None: return self.underlines = [u + shift for u in self.underlines]
def __add__(self, other): # need to handle shifting of indices if isinstance(other, LabelCollection): to_add = deepcopy(other) to_add.shift_underlines(len(self)) else: to_add = other result = super().__add__(to_add) # carry through underlines with addition if isinstance(result, LabelCollection): underlines = _add_if_not_none(self.underlines, to_add.underlines) result.underlines = underlines return result def __radd__(self, other): # need to handle shifting of indices to_add = deepcopy(self) to_add.shift_underlines(len(self)) result = RowAddMixin.radd(to_add, other) # carry through underlines with addition if isinstance(result, LabelCollection): if isinstance(other, LabelCollection): underlines = _add_if_not_none(to_add.underlines, other.underlines) else: underlines = to_add.underlines result.underlines = underlines return result
[docs] def pad(self, length: int, direction='right'): """ Expand row out to the right or left with blanks, until it is length passed :param length: :return: """ # only necessary to move underline columns if padding left if direction == 'left': num_values_to_add = length - len(self) self.shift_underlines(num_values_to_add) super().pad(length=length, direction=direction)
[docs] def pop_left(self) -> Label: label = self.values.pop(0) self.shift_underlines(-1) return label
def _get_item_length(item): if isinstance(item, Label): return len(item) else: return 1 def _convert_underline_to_label_index_list(underline: Union[int, str]=None): if underline is None: return [] assert isinstance(underline, (int, str)) if isinstance(underline, int): return [underline] # underline is str int_list: List[int] = [] for part in underline.split(): # handle each space separated part. if just a range, will only be one part # check if is range like '3-5' try: if _is_range_str(part): int_list += _range_str_to_int_list(part) else: int_list.append(int(part)) except ValueError: raise NotImplementedError(f'could not parse underline str into int. full underline ' f'str: {underline}. failed processing part: {part}') return int_list def _range_str_to_int_list(underline: str): bottom, top = underline.split('-') return [i for i in range(int(bottom), int(top) + 1)] def _is_range_str(underline: str): pattern = re.compile(r'\d+-\d+') if pattern.match(underline): return True else: return False