Rename and add attribute setting/getting
This commit is contained in:
parent
96f29a49cf
commit
2eb26c5532
1 changed files with 108 additions and 21 deletions
129
cheuph/markup.py
129
cheuph/markup.py
|
|
@ -1,7 +1,6 @@
|
|||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
__all__ = ["Attributes", "Chunk", "MarkedUp"]
|
||||
from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
|
||||
|
||||
__all__ = ["Attributes", "Chunk", "AttributedText"]
|
||||
|
||||
Attributes = Dict[str, Any]
|
||||
|
||||
|
|
@ -58,7 +57,7 @@ class Chunk:
|
|||
return self._text
|
||||
|
||||
@property
|
||||
def attrs(self) -> Attributes:
|
||||
def attributes(self) -> Attributes:
|
||||
return dict(self._attributes)
|
||||
|
||||
# Private methods
|
||||
|
|
@ -66,17 +65,20 @@ class Chunk:
|
|||
def _join(self, chunk: "Chunk") -> Optional["Chunk"]:
|
||||
if self._attributes == chunk._attributes:
|
||||
return Chunk(self.text + chunk.text, self._attributes)
|
||||
else:
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
# Public methods
|
||||
|
||||
def with_attribute(self, name: str, value: Any) -> "Chunk":
|
||||
def get(self, name: str, default: Any = None) -> Any:
|
||||
return self.attributes.get(name, default)
|
||||
|
||||
def set(self, name: str, value: Any) -> "Chunk":
|
||||
new_attributes = dict(self._attributes)
|
||||
new_attributes[name] = value
|
||||
return Chunk(self.text, new_attributes)
|
||||
|
||||
def without_attribute(self, name: str) -> "Chunk":
|
||||
def remove(self, name: str) -> "Chunk":
|
||||
new_attributes = dict(self._attributes)
|
||||
|
||||
# This removes the value with that key, if it exists, and does nothing
|
||||
|
|
@ -87,16 +89,16 @@ class Chunk:
|
|||
return Chunk(self.text, new_attributes)
|
||||
|
||||
|
||||
class MarkedUp:
|
||||
class AttributedText:
|
||||
"""
|
||||
Objects of this class are immutable and behave str-like. Supported
|
||||
operations are len, + and splicing.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def from_chunks(cls, chunks: List[Chunk]) -> "MarkedUp":
|
||||
def from_chunks(cls, chunks: Iterable[Chunk]) -> "AttributedText":
|
||||
new = cls()
|
||||
new._chunks = Chunk.join_chunks(chunks)
|
||||
new._chunks = Chunk.join_chunks(list(chunks))
|
||||
return new
|
||||
|
||||
# Common special methods
|
||||
|
|
@ -107,17 +109,17 @@ class MarkedUp:
|
|||
self._chunks.append(Chunk(text))
|
||||
|
||||
def __str__(self) -> str:
|
||||
return "".join(map(str, self._chunks))
|
||||
return self.text
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"MarkedUp.from_chunks({self._chunks!r})"
|
||||
return f"AttributedText.from_chunks({self._chunks!r})"
|
||||
|
||||
# Uncommon special methods
|
||||
|
||||
def __add__(self, other: "MarkedUp") -> "MarkedUp":
|
||||
return MarkedUp.from_chunks(self._pieces + other._pieces)
|
||||
def __add__(self, other: "AttributedText") -> "AttributedText":
|
||||
return AttributedText.from_chunks(self._chunks + other._chunks)
|
||||
|
||||
def __getitem__(self, key: Union[int, slice]) -> "MarkedUp":
|
||||
def __getitem__(self, key: Union[int, slice]) -> "AttributedText":
|
||||
chunks: List[Chunk]
|
||||
|
||||
if isinstance(key, slice):
|
||||
|
|
@ -125,13 +127,17 @@ class MarkedUp:
|
|||
else:
|
||||
chunks = [self._at(key)]
|
||||
|
||||
return MarkedUp.from_chunks(chunks)
|
||||
return AttributedText.from_chunks(chunks)
|
||||
|
||||
def __len__(self) -> int:
|
||||
return sum(map(len, self._chunks))
|
||||
|
||||
# Properties
|
||||
|
||||
@property
|
||||
def text(self) -> str:
|
||||
return "".join(chunk.text for chunk in self._chunks)
|
||||
|
||||
@property
|
||||
def chunks(self) -> List[Chunk]:
|
||||
return list(self._chunks)
|
||||
|
|
@ -150,8 +156,9 @@ class MarkedUp:
|
|||
return chunk[chunk_key]
|
||||
|
||||
pos += len(chunk)
|
||||
else:
|
||||
raise KeyError
|
||||
|
||||
# We haven't found the chunk
|
||||
raise KeyError
|
||||
|
||||
def _slice(self, key: slice) -> List[Chunk]:
|
||||
start, stop, step = key.start, key.stop, key.step
|
||||
|
|
@ -194,5 +201,85 @@ class MarkedUp:
|
|||
|
||||
# Public methods
|
||||
|
||||
def attrs(self, key: int) -> Attributes:
|
||||
return self._at(key).attrs
|
||||
def at(self, pos: int) -> Attributes:
|
||||
return self._at(pos).attributes
|
||||
|
||||
def get(self,
|
||||
pos: int,
|
||||
name: str,
|
||||
default: Any = None,
|
||||
) -> Any:
|
||||
return self._at(pos).get(name, default)
|
||||
|
||||
# "find all separate blocks with this property"
|
||||
def find_all(self, name: str) -> List[Tuple[Any, int, int]]:
|
||||
blocks = []
|
||||
pos = 0
|
||||
block = None
|
||||
|
||||
for chunk in self._chunks:
|
||||
if name in chunk.attributes:
|
||||
attribute = chunk.attributes[name]
|
||||
start = pos
|
||||
stop = pos + len(chunk)
|
||||
|
||||
if block is None:
|
||||
block = (attribute, start, stop)
|
||||
continue
|
||||
|
||||
block_attr, block_start, _ = block
|
||||
|
||||
if block_attr == attribute:
|
||||
block = (attribute, block_start, stop)
|
||||
else:
|
||||
blocks.append(block)
|
||||
block = (attribute, start, stop)
|
||||
|
||||
pos += len(chunk)
|
||||
|
||||
if block is not None:
|
||||
blocks.append(block)
|
||||
|
||||
return blocks
|
||||
|
||||
def set(self,
|
||||
name: str,
|
||||
value: Any,
|
||||
start: Optional[int] = None,
|
||||
stop: Optional[int] = None,
|
||||
) -> "AttributedText":
|
||||
if start is None and stop is None:
|
||||
chunks = (chunk.set(name, value) for chunk in self._chunks)
|
||||
return AttributedText.from_chunks(chunks)
|
||||
elif start is None:
|
||||
return self[:stop].set(name, value) + self[stop:]
|
||||
elif stop is None:
|
||||
return self[:start] + self[start:].set(name, value)
|
||||
elif start > stop:
|
||||
# set value everywhere BUT the specified interval
|
||||
return self.set(name, value, stop=stop).set(name, value, start=start)
|
||||
else:
|
||||
middle = self[start:stop].set(name, value)
|
||||
return self[:start] + middle + self[stop:]
|
||||
|
||||
def set_at(self, name: str, value: Any, pos: int) -> "AttributedText":
|
||||
return self.set(name, value, pos, pos)
|
||||
|
||||
def remove(self,
|
||||
name: str,
|
||||
start: Optional[int] = None,
|
||||
stop: Optional[int] = None,
|
||||
) -> "AttributedText":
|
||||
if start is None and stop is None:
|
||||
chunks = (chunk.remove(name) for chunk in self._chunks)
|
||||
return AttributedText.from_chunks(chunks)
|
||||
elif start is None:
|
||||
return self[:stop].remove(name) + self[stop:]
|
||||
elif stop is None:
|
||||
return self[:start] + self[start:].remove(name)
|
||||
elif start > stop:
|
||||
# remove value everywhere BUT the specified interval
|
||||
return self.remove(name, stop=stop).remove(name, start=start)
|
||||
else:
|
||||
middle = self[start:stop].remove(name)
|
||||
return self[:start] + middle + self[stop:]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue