Cleaning up GlobalScriptContainer from some junk logic.
This commit is contained in:
parent
376d1d1ec3
commit
c91822606f
5 changed files with 1 additions and 708 deletions
|
|
@ -186,7 +186,6 @@ def _init(portal_mode=False):
|
||||||
from .typeclasses.tags import TagCategoryProperty, TagProperty
|
from .typeclasses.tags import TagCategoryProperty, TagProperty
|
||||||
from .utils import ansi, gametime, logger
|
from .utils import ansi, gametime, logger
|
||||||
from .utils.ansi import ANSIString
|
from .utils.ansi import ANSIString
|
||||||
from .utils.evrich import install as install_evrich
|
|
||||||
|
|
||||||
# containers
|
# containers
|
||||||
from .utils.containers import GLOBAL_SCRIPTS, OPTION_CLASSES
|
from .utils.containers import GLOBAL_SCRIPTS, OPTION_CLASSES
|
||||||
|
|
@ -376,9 +375,6 @@ def _init(portal_mode=False):
|
||||||
del SystemCmds
|
del SystemCmds
|
||||||
del _EvContainer
|
del _EvContainer
|
||||||
|
|
||||||
# Trigger EvRich to monkey-patch Rich in-memory.
|
|
||||||
install_evrich()
|
|
||||||
|
|
||||||
# delayed starts - important so as to not back-access evennia before it has
|
# delayed starts - important so as to not back-access evennia before it has
|
||||||
# finished initializing
|
# finished initializing
|
||||||
if not portal_mode:
|
if not portal_mode:
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,6 @@ from evennia.scripts.monitorhandler import MONITOR_HANDLER
|
||||||
from evennia.typeclasses.attributes import AttributeHandler, DbHolder, InMemoryAttributeBackend
|
from evennia.typeclasses.attributes import AttributeHandler, DbHolder, InMemoryAttributeBackend
|
||||||
from evennia.utils import logger
|
from evennia.utils import logger
|
||||||
from evennia.utils.utils import class_from_module, lazy_property, make_iter
|
from evennia.utils.utils import class_from_module, lazy_property, make_iter
|
||||||
from evennia.utils.evrich import MudConsole, MudConsoleOptions
|
|
||||||
from rich.color import ColorSystem
|
|
||||||
|
|
||||||
_GA = object.__getattribute__
|
_GA = object.__getattribute__
|
||||||
_SA = object.__setattr__
|
_SA = object.__setattr__
|
||||||
|
|
@ -53,57 +51,6 @@ class ServerSession(_BASE_SESSION_CLASS):
|
||||||
self.cmdset_storage_string = ""
|
self.cmdset_storage_string = ""
|
||||||
self.cmdset = CmdSetHandler(self, True)
|
self.cmdset = CmdSetHandler(self, True)
|
||||||
|
|
||||||
@lazy_property
|
|
||||||
def console(self):
|
|
||||||
from mudrich import MudConsole
|
|
||||||
if "SCREENWIDTH" in self.protocol_flags:
|
|
||||||
width = self.protocol_flags["SCREENWIDTH"][0]
|
|
||||||
else:
|
|
||||||
width = 78
|
|
||||||
return MudConsole(color_system=self.rich_color_system(), width=width,
|
|
||||||
file=self, record=True)
|
|
||||||
|
|
||||||
def rich_color_system(self):
|
|
||||||
if self.protocol_flags.get("NOCOLOR", False):
|
|
||||||
return None
|
|
||||||
if self.protocol_flags.get("XTERM256", False):
|
|
||||||
return "256"
|
|
||||||
if self.protocol_flags.get("ANSI", False):
|
|
||||||
return "standard"
|
|
||||||
return None
|
|
||||||
|
|
||||||
def update_rich(self):
|
|
||||||
check = self.console
|
|
||||||
if "SCREENWIDTH" in self.protocol_flags:
|
|
||||||
self.console._width = self.protocol_flags["SCREENWIDTH"][0]
|
|
||||||
else:
|
|
||||||
self.console._width = 80
|
|
||||||
if self.protocol_flags.get("NOCOLOR", False):
|
|
||||||
self.console._color_system = None
|
|
||||||
elif self.protocol_flags.get("XTERM256", False):
|
|
||||||
self.console._color_system = ColorSystem.EIGHT_BIT
|
|
||||||
elif self.protocol_flags.get("ANSI", False):
|
|
||||||
self.console._color_system = ColorSystem.STANDARD
|
|
||||||
|
|
||||||
def write(self, b: str):
|
|
||||||
"""
|
|
||||||
When self.console.print() is called, it writes output to here.
|
|
||||||
Not necessarily useful, but it ensures console print doesn't end up sent out stdout or etc.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def flush(self):
|
|
||||||
"""
|
|
||||||
Do not remove this method. It's needed to trick Console into treating this object
|
|
||||||
as a file.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def print(self, *args, **kwargs) -> str:
|
|
||||||
"""
|
|
||||||
A thin wrapper around Rich.Console's print. Returns the exported data.
|
|
||||||
"""
|
|
||||||
self.console.print(*args, highlight=False, **kwargs)
|
|
||||||
return self.console.export_text(clear=True, styles=True)
|
|
||||||
|
|
||||||
def __cmdset_storage_get(self):
|
def __cmdset_storage_get(self):
|
||||||
return [path.strip() for path in self.cmdset_storage_string.split(",")]
|
return [path.strip() for path in self.cmdset_storage_string.split(",")]
|
||||||
|
|
||||||
|
|
@ -503,6 +450,3 @@ class ServerSession(_BASE_SESSION_CLASS):
|
||||||
else:
|
else:
|
||||||
return f"{self.protocol_key}({self.address})"
|
return f"{self.protocol_key}({self.address})"
|
||||||
|
|
||||||
def load_sync_data(self, sessdata):
|
|
||||||
super().load_sync_data(sessdata)
|
|
||||||
self.update_rich()
|
|
||||||
|
|
|
||||||
|
|
@ -72,10 +72,6 @@ from evennia.utils.utils import to_str
|
||||||
|
|
||||||
MXP_ENABLED = settings.MXP_ENABLED
|
MXP_ENABLED = settings.MXP_ENABLED
|
||||||
|
|
||||||
from rich.ansi import AnsiDecoder
|
|
||||||
from .evrich import MudText
|
|
||||||
|
|
||||||
|
|
||||||
# ANSI definitions
|
# ANSI definitions
|
||||||
|
|
||||||
ANSI_BEEP = "\07"
|
ANSI_BEEP = "\07"
|
||||||
|
|
@ -1057,13 +1053,6 @@ class ANSIString(str, metaclass=ANSIMeta):
|
||||||
result += self._raw_string[index]
|
result += self._raw_string[index]
|
||||||
return ANSIString(result + clean + append_tail, decoded=True)
|
return ANSIString(result + clean + append_tail, decoded=True)
|
||||||
|
|
||||||
def __rich_console__(self, console, options):
|
|
||||||
"""
|
|
||||||
Implements the Rich console API, allowing AnsiStrings to be
|
|
||||||
converted to MudText instances.
|
|
||||||
"""
|
|
||||||
yield MudText("\n").join(AnsiDecoder().decode(self))
|
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
"""
|
"""
|
||||||
Return a string object *without* the ANSI escapes.
|
Return a string object *without* the ANSI escapes.
|
||||||
|
|
|
||||||
|
|
@ -1,635 +0,0 @@
|
||||||
"""
|
|
||||||
This module installs monkey patches to Rich, allowing it to support MXP.
|
|
||||||
|
|
||||||
MudRich system, by Volund, ported the hard way to Evennia.
|
|
||||||
"""
|
|
||||||
import html
|
|
||||||
from dataclasses import dataclass
|
|
||||||
import random
|
|
||||||
import re
|
|
||||||
from marshal import loads, dumps
|
|
||||||
|
|
||||||
from typing import Any, Dict, Iterable, List, Optional, Type, Union, Tuple
|
|
||||||
|
|
||||||
from rich.color import Color, ColorSystem
|
|
||||||
|
|
||||||
from rich.style import Style as OLD_STYLE
|
|
||||||
from rich.text import Text as OLD_TEXT, Segment, Span
|
|
||||||
from rich.console import Console as OLD_CONSOLE, ConsoleOptions as OLD_CONSOLE_OPTIONS, NoChange, NO_CHANGE
|
|
||||||
from rich.console import JustifyMethod, OverflowMethod
|
|
||||||
|
|
||||||
|
|
||||||
_RE_SQUISH = re.compile("\S+")
|
|
||||||
_RE_NOTSPACE = re.compile("[^ ]+")
|
|
||||||
|
|
||||||
|
|
||||||
class MudStyle(OLD_STYLE):
|
|
||||||
_tag: str
|
|
||||||
|
|
||||||
__slots__ = [
|
|
||||||
"_tag",
|
|
||||||
"_xml_attr",
|
|
||||||
"_xml_attr_data"
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
*,
|
|
||||||
color: Optional[Union[Color, str]] = None,
|
|
||||||
bgcolor: Optional[Union[Color, str]] = None,
|
|
||||||
bold: Optional[bool] = None,
|
|
||||||
dim: Optional[bool] = None,
|
|
||||||
italic: Optional[bool] = None,
|
|
||||||
underline: Optional[bool] = None,
|
|
||||||
blink: Optional[bool] = None,
|
|
||||||
blink2: Optional[bool] = None,
|
|
||||||
reverse: Optional[bool] = None,
|
|
||||||
conceal: Optional[bool] = None,
|
|
||||||
strike: Optional[bool] = None,
|
|
||||||
underline2: Optional[bool] = None,
|
|
||||||
frame: Optional[bool] = None,
|
|
||||||
encircle: Optional[bool] = None,
|
|
||||||
overline: Optional[bool] = None,
|
|
||||||
link: Optional[str] = None,
|
|
||||||
meta: Optional[Dict[str, Any]] = None,
|
|
||||||
tag: Optional[str] = None,
|
|
||||||
xml_attr: Optional[Dict] = None,
|
|
||||||
):
|
|
||||||
super().__init__(color=color, bgcolor=bgcolor, bold=bold, dim=dim, italic=italic,
|
|
||||||
underline=underline, blink=blink, blink2=blink2, reverse=reverse,
|
|
||||||
conceal=conceal, strike=strike, underline2=underline2, frame=frame,
|
|
||||||
encircle=encircle, overline=overline, link=link, meta=meta)
|
|
||||||
|
|
||||||
self._tag = tag
|
|
||||||
self._xml_attr = xml_attr
|
|
||||||
if self._xml_attr:
|
|
||||||
self._xml_attr_data = (
|
|
||||||
" ".join(f'{k}="{html.escape(v)}"' for k, v in xml_attr.items())
|
|
||||||
if xml_attr
|
|
||||||
else ""
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self._xml_attr_data = ""
|
|
||||||
|
|
||||||
self._hash = hash(
|
|
||||||
(
|
|
||||||
self._color,
|
|
||||||
self._bgcolor,
|
|
||||||
self._attributes,
|
|
||||||
self._set_attributes,
|
|
||||||
link,
|
|
||||||
self._meta,
|
|
||||||
tag,
|
|
||||||
self._xml_attr_data
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
self._null = not (self._set_attributes or color or bgcolor or link or meta or tag)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def upgrade(cls, old):
|
|
||||||
return cls.parse(str(old))
|
|
||||||
|
|
||||||
def render(
|
|
||||||
self,
|
|
||||||
text: str = "",
|
|
||||||
*,
|
|
||||||
color_system: Optional[ColorSystem] = ColorSystem.TRUECOLOR,
|
|
||||||
legacy_windows: bool = False,
|
|
||||||
mxp: bool = False,
|
|
||||||
pueblo: bool = False,
|
|
||||||
links: bool = True,
|
|
||||||
) -> str:
|
|
||||||
"""Render the ANSI codes for the style.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
text (str, optional): A string to style. Defaults to "".
|
|
||||||
color_system (Optional[ColorSystem], optional): Color system to render to. Defaults to ColorSystem.TRUECOLOR.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: A string containing ANSI style codes.
|
|
||||||
"""
|
|
||||||
out_text = text
|
|
||||||
if mxp:
|
|
||||||
out_text = html.escape(out_text)
|
|
||||||
if not out_text:
|
|
||||||
return out_text
|
|
||||||
if color_system is not None:
|
|
||||||
attrs = self._make_ansi_codes(color_system)
|
|
||||||
rendered = f"\x1b[{attrs}m{out_text}\x1b[0m" if attrs else out_text
|
|
||||||
else:
|
|
||||||
rendered = out_text
|
|
||||||
if links and self._link and not legacy_windows:
|
|
||||||
rendered = (
|
|
||||||
f"\x1b]8;id={self._link_id};{self._link}\x1b\\{rendered}\x1b]8;;\x1b\\"
|
|
||||||
)
|
|
||||||
if (pueblo or mxp) and self._tag:
|
|
||||||
if mxp:
|
|
||||||
if self._xml_attr:
|
|
||||||
rendered = f"\x1b[4z<{self._tag} {self._xml_attr_data}>{rendered}\x1b[4z</{self._tag}>"
|
|
||||||
else:
|
|
||||||
rendered = f"\x1b[4z<{self._tag}>{rendered}\x1b[4z</{self._tag}>"
|
|
||||||
else:
|
|
||||||
if self._xml_attr:
|
|
||||||
rendered = (
|
|
||||||
f"{self._tag} {self._xml_attr_data}>{rendered}</{self._tag}>"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
rendered = f"<{self._tag}>{rendered}</{self._tag}>"
|
|
||||||
return rendered
|
|
||||||
|
|
||||||
def __add__(self, style: Union["Style", str]) -> "Style":
|
|
||||||
if isinstance(style, str):
|
|
||||||
style = self.__class__.parse(style)
|
|
||||||
if not (isinstance(style, MudStyle) or style is None):
|
|
||||||
return NotImplemented
|
|
||||||
if style is None or style._null:
|
|
||||||
return self
|
|
||||||
if self._null:
|
|
||||||
return style
|
|
||||||
new_style: MudStyle = self.__new__(MudStyle)
|
|
||||||
new_style._ansi = None
|
|
||||||
new_style._style_definition = None
|
|
||||||
new_style._color = style._color or self._color
|
|
||||||
new_style._bgcolor = style._bgcolor or self._bgcolor
|
|
||||||
new_style._attributes = (self._attributes & ~style._set_attributes) | (
|
|
||||||
style._attributes & style._set_attributes
|
|
||||||
)
|
|
||||||
new_style._set_attributes = self._set_attributes | style._set_attributes
|
|
||||||
new_style._link = style._link or self._link
|
|
||||||
new_style._link_id = style._link_id or self._link_id
|
|
||||||
|
|
||||||
new_style._tag = None
|
|
||||||
if hasattr(style, "_tag") and hasattr(self, "_tag"):
|
|
||||||
new_style._tag = style._tag or self._tag
|
|
||||||
|
|
||||||
new_style._xml_attr = None
|
|
||||||
if hasattr(style, "_xml_attr") and hasattr(self, "_xml_attr"):
|
|
||||||
new_style._xml_attr = style._xml_attr or self._xml_attr
|
|
||||||
|
|
||||||
new_style._xml_attr_data = ""
|
|
||||||
if hasattr(style, "_xml_attr_data") and hasattr(self, "_xml_attr_data"):
|
|
||||||
new_style._xml_attr_data = style._xml_attr_data or self._xml_attr_data
|
|
||||||
|
|
||||||
new_style._hash = style._hash
|
|
||||||
new_style._null = self._null or style._null
|
|
||||||
if self._meta and style._meta:
|
|
||||||
new_style._meta = dumps({**self.meta, **style.meta})
|
|
||||||
else:
|
|
||||||
new_style._meta = self._meta or style._meta
|
|
||||||
|
|
||||||
return new_style
|
|
||||||
|
|
||||||
def __radd__(self, other):
|
|
||||||
if isinstance(other, str):
|
|
||||||
other = self.__class__.parse(other)
|
|
||||||
return other + self
|
|
||||||
return NotImplemented
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class MudConsoleOptions(OLD_CONSOLE_OPTIONS):
|
|
||||||
mxp: Optional[bool] = False
|
|
||||||
"""Enable MXP/MUD HTML when printing. For MUDs only."""
|
|
||||||
pueblo: Optional[bool] = False
|
|
||||||
"""Enable Pueblo/MUD HTML when printing. For MUDs only."""
|
|
||||||
links: Optional[bool] = True
|
|
||||||
"""Enable ANSI Links when printing. Turn off if MXP/Pueblo is on."""
|
|
||||||
|
|
||||||
def update(
|
|
||||||
self,
|
|
||||||
*,
|
|
||||||
width: Union[int, NoChange] = NO_CHANGE,
|
|
||||||
min_width: Union[int, NoChange] = NO_CHANGE,
|
|
||||||
max_width: Union[int, NoChange] = NO_CHANGE,
|
|
||||||
justify: Union[Optional[JustifyMethod], NoChange] = NO_CHANGE,
|
|
||||||
overflow: Union[Optional[OverflowMethod], NoChange] = NO_CHANGE,
|
|
||||||
no_wrap: Union[Optional[bool], NoChange] = NO_CHANGE,
|
|
||||||
highlight: Union[Optional[bool], NoChange] = NO_CHANGE,
|
|
||||||
markup: Union[Optional[bool], NoChange] = NO_CHANGE,
|
|
||||||
height: Union[Optional[int], NoChange] = NO_CHANGE,
|
|
||||||
mxp: Union[Optional[bool], NoChange] = NO_CHANGE,
|
|
||||||
pueblo: Union[Optional[bool], NoChange] = NO_CHANGE,
|
|
||||||
links: Union[Optional[bool], NoChange] = NO_CHANGE,
|
|
||||||
) -> "ConsoleOptions":
|
|
||||||
"""Update values, return a copy."""
|
|
||||||
options = self.copy()
|
|
||||||
if not isinstance(width, NoChange):
|
|
||||||
options.min_width = options.max_width = max(0, width)
|
|
||||||
if not isinstance(min_width, NoChange):
|
|
||||||
options.min_width = min_width
|
|
||||||
if not isinstance(max_width, NoChange):
|
|
||||||
options.max_width = max_width
|
|
||||||
if not isinstance(justify, NoChange):
|
|
||||||
options.justify = justify
|
|
||||||
if not isinstance(overflow, NoChange):
|
|
||||||
options.overflow = overflow
|
|
||||||
if not isinstance(no_wrap, NoChange):
|
|
||||||
options.no_wrap = no_wrap
|
|
||||||
if not isinstance(highlight, NoChange):
|
|
||||||
options.highlight = highlight
|
|
||||||
if not isinstance(markup, NoChange):
|
|
||||||
options.markup = markup
|
|
||||||
if not isinstance(height, NoChange):
|
|
||||||
options.height = None if height is None else max(0, height)
|
|
||||||
if not isinstance(mxp, NoChange):
|
|
||||||
options.mxp = mxp
|
|
||||||
if not isinstance(pueblo, NoChange):
|
|
||||||
options.pueblo = pueblo
|
|
||||||
if not isinstance(links, NoChange):
|
|
||||||
options.links = links
|
|
||||||
return options
|
|
||||||
|
|
||||||
|
|
||||||
class MudConsole(OLD_CONSOLE):
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
mxp = kwargs.pop("mxp", False)
|
|
||||||
pueblo = kwargs.pop("pueblo", False)
|
|
||||||
links = kwargs.pop("links", False)
|
|
||||||
super().__init__(**kwargs)
|
|
||||||
|
|
||||||
self._mxp = mxp
|
|
||||||
self._pueblo = pueblo
|
|
||||||
self._links = links
|
|
||||||
|
|
||||||
def export_text(self, *, clear: bool = True, styles: bool = False) -> str:
|
|
||||||
"""Generate text from console contents (requires record=True argument in constructor).
|
|
||||||
Args:
|
|
||||||
clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``.
|
|
||||||
styles (bool, optional): If ``True``, ansi escape codes will be included. ``False`` for plain text.
|
|
||||||
Defaults to ``False``.
|
|
||||||
Returns:
|
|
||||||
str: String containing console contents.
|
|
||||||
"""
|
|
||||||
assert (
|
|
||||||
self.record
|
|
||||||
), "To export console contents set record=True in the constructor or instance"
|
|
||||||
|
|
||||||
with self._record_buffer_lock:
|
|
||||||
if styles:
|
|
||||||
text = "".join(
|
|
||||||
(style.render(
|
|
||||||
text,
|
|
||||||
color_system=self.color_system,
|
|
||||||
legacy_windows=self.legacy_windows,
|
|
||||||
mxp=self._mxp,
|
|
||||||
pueblo=self._pueblo,
|
|
||||||
links=self._links,
|
|
||||||
) if style else text)
|
|
||||||
for text, style, _ in self._record_buffer
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
text = "".join(
|
|
||||||
segment.text
|
|
||||||
for segment in self._record_buffer
|
|
||||||
if not segment.control
|
|
||||||
)
|
|
||||||
if clear:
|
|
||||||
del self._record_buffer[:]
|
|
||||||
return text
|
|
||||||
|
|
||||||
def _render_buffer(self, buffer: Iterable[Segment]) -> str:
|
|
||||||
"""Render buffered output, and clear buffer."""
|
|
||||||
output: List[str] = []
|
|
||||||
append = output.append
|
|
||||||
color_system = self._color_system
|
|
||||||
legacy_windows = self.legacy_windows
|
|
||||||
not_terminal = not self.is_terminal
|
|
||||||
if self.no_color and color_system:
|
|
||||||
buffer = Segment.remove_color(buffer)
|
|
||||||
for text, style, control in buffer:
|
|
||||||
if style:
|
|
||||||
append(
|
|
||||||
style.render(
|
|
||||||
text,
|
|
||||||
color_system=color_system,
|
|
||||||
legacy_windows=legacy_windows,
|
|
||||||
mxp=self._mxp,
|
|
||||||
pueblo=self._pueblo,
|
|
||||||
links=self._links,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
elif not (not_terminal and control):
|
|
||||||
append(text)
|
|
||||||
|
|
||||||
rendered = "".join(output)
|
|
||||||
return rendered
|
|
||||||
|
|
||||||
|
|
||||||
class MudText(OLD_TEXT):
|
|
||||||
|
|
||||||
def __radd__(self, other):
|
|
||||||
if isinstance(other, str):
|
|
||||||
other = self.__class__(text=other)
|
|
||||||
return other + self
|
|
||||||
return NotImplemented
|
|
||||||
|
|
||||||
def __iadd__(self, other: Any) -> "Text":
|
|
||||||
if isinstance(other, (str, OLD_TEXT)):
|
|
||||||
self.append(other)
|
|
||||||
return self
|
|
||||||
return NotImplemented
|
|
||||||
|
|
||||||
def __mul__(self, other):
|
|
||||||
if not isinstance(other, int):
|
|
||||||
return self
|
|
||||||
if other <= 0:
|
|
||||||
return self.__class__()
|
|
||||||
if other == 1:
|
|
||||||
return self.copy()
|
|
||||||
if other > 1:
|
|
||||||
out = self.copy()
|
|
||||||
for i in range(other - 1):
|
|
||||||
out.append(self)
|
|
||||||
return out
|
|
||||||
|
|
||||||
def __rmul__(self, other):
|
|
||||||
if not isinstance(other, int):
|
|
||||||
return self
|
|
||||||
return self * other
|
|
||||||
|
|
||||||
def __format__(self, format_spec):
|
|
||||||
"""
|
|
||||||
Allows use of f-strings, although styling is not preserved.
|
|
||||||
"""
|
|
||||||
return self.plain.__format__(format_spec)
|
|
||||||
|
|
||||||
# Begin implementing Python String Api below...
|
|
||||||
|
|
||||||
def capitalize(self):
|
|
||||||
return self.__class__(text=self.plain.capitalize(), style=self.style, spans=list(self.spans))
|
|
||||||
|
|
||||||
def count(self, *args, **kwargs):
|
|
||||||
return self.plain.count(*args, **kwargs)
|
|
||||||
|
|
||||||
def startswith(self, *args, **kwargs):
|
|
||||||
return self.plain.startswith(*args, **kwargs)
|
|
||||||
|
|
||||||
def endswith(self, *args, **kwargs):
|
|
||||||
return self.plain.endswith(*args, **kwargs)
|
|
||||||
|
|
||||||
def find(self, *args, **kwargs):
|
|
||||||
return self.plain.find(*args, **kwargs)
|
|
||||||
|
|
||||||
def index(self, *args, **kwargs):
|
|
||||||
return self.plain.index(*args, **kwargs)
|
|
||||||
|
|
||||||
def isalnum(self):
|
|
||||||
return self.plain.isalnum()
|
|
||||||
|
|
||||||
def isalpha(self):
|
|
||||||
return self.plain.isalpha()
|
|
||||||
|
|
||||||
def isdecimal(self):
|
|
||||||
return self.plain.isdecimal()
|
|
||||||
|
|
||||||
def isdigit(self):
|
|
||||||
return self.plain.isdigit()
|
|
||||||
|
|
||||||
def isidentifier(self):
|
|
||||||
return self.plain.isidentifier()
|
|
||||||
|
|
||||||
def islower(self):
|
|
||||||
return self.plain.islower()
|
|
||||||
|
|
||||||
def isnumeric(self):
|
|
||||||
return self.plain.isnumeric()
|
|
||||||
|
|
||||||
def isprintable(self):
|
|
||||||
return self.plain.isprintable()
|
|
||||||
|
|
||||||
def isspace(self):
|
|
||||||
return self.plain.isspace()
|
|
||||||
|
|
||||||
def istitle(self):
|
|
||||||
return self.plain.istitle()
|
|
||||||
|
|
||||||
def isupper(self):
|
|
||||||
return self.plain.isupper()
|
|
||||||
|
|
||||||
def center(self, width, fillchar=" "):
|
|
||||||
changed = self.plain.center(width, fillchar)
|
|
||||||
start = changed.find(self.plain)
|
|
||||||
lside = changed[:start]
|
|
||||||
rside = changed[len(lside) + len(self.plain):]
|
|
||||||
idx = self.disassemble_bits()
|
|
||||||
new_idx = list()
|
|
||||||
for c in lside:
|
|
||||||
new_idx.append((None, c))
|
|
||||||
new_idx.extend(idx)
|
|
||||||
for c in rside:
|
|
||||||
new_idx.append((None, c))
|
|
||||||
return self.__class__.assemble_bits(new_idx)
|
|
||||||
|
|
||||||
def ljust(self, width: int, fillchar: Union[str, "MudText"] = " "):
|
|
||||||
diff = width - len(self)
|
|
||||||
out = self.copy()
|
|
||||||
if diff <= 0:
|
|
||||||
return out
|
|
||||||
else:
|
|
||||||
if isinstance(fillchar, str):
|
|
||||||
fillchar = self.__class__(fillchar)
|
|
||||||
out.append(fillchar * diff)
|
|
||||||
return out
|
|
||||||
|
|
||||||
def rjust(self, width: int, fillchar: Union[str, "MudText"] = " "):
|
|
||||||
diff = width - len(self)
|
|
||||||
if diff <= 0:
|
|
||||||
return self.copy()
|
|
||||||
else:
|
|
||||||
if isinstance(fillchar, str):
|
|
||||||
fillchar = self.__class__(fillchar)
|
|
||||||
out = fillchar * diff
|
|
||||||
out.append(self)
|
|
||||||
return out
|
|
||||||
|
|
||||||
def lstrip(self, chars: str = None):
|
|
||||||
lstripped = self.plain.lstrip(chars)
|
|
||||||
strip_count = len(self.plain) - len(lstripped)
|
|
||||||
return self[strip_count:]
|
|
||||||
|
|
||||||
def strip(self, chars: str = " "):
|
|
||||||
out_map = self.disassemble_bits()
|
|
||||||
for i, e in enumerate(out_map):
|
|
||||||
if e[1] != chars:
|
|
||||||
out_map = out_map[i:]
|
|
||||||
break
|
|
||||||
out_map.reverse()
|
|
||||||
for i, e in enumerate(out_map):
|
|
||||||
if e[1] != chars:
|
|
||||||
out_map = out_map[i:]
|
|
||||||
break
|
|
||||||
out_map.reverse()
|
|
||||||
return self.__class__.assemble_bits(out_map)
|
|
||||||
|
|
||||||
def replace(self, old: str, new: Union[str, "Text"], count=None) -> "Text":
|
|
||||||
if not (indexes := self.find_all(old)):
|
|
||||||
return self.clone()
|
|
||||||
if count and count > 0:
|
|
||||||
indexes = indexes[:count]
|
|
||||||
old_len = len(old)
|
|
||||||
new_len = len(new)
|
|
||||||
other = self.clone()
|
|
||||||
markup_idx_map = self.disassemble_bits()
|
|
||||||
other_map = other.disassemble_bits()
|
|
||||||
|
|
||||||
for idx in reversed(indexes):
|
|
||||||
final_markup = markup_idx_map[idx + old_len][0]
|
|
||||||
diff = abs(old_len - new_len)
|
|
||||||
replace_chars = min(new_len, old_len)
|
|
||||||
# First, replace any characters that overlap.
|
|
||||||
for i in range(replace_chars):
|
|
||||||
other_map[idx + i] = (markup_idx_map[idx + i][0], new[i])
|
|
||||||
if old_len == new_len:
|
|
||||||
pass # the nicest case. nothing else needs doing.
|
|
||||||
elif old_len > new_len:
|
|
||||||
# slightly complex. pop off remaining characters.
|
|
||||||
for i in range(diff):
|
|
||||||
deleted = other_map.pop(idx + new_len)
|
|
||||||
elif new_len > old_len:
|
|
||||||
# slightly complex. insert new characters.
|
|
||||||
for i in range(diff):
|
|
||||||
other_map.insert(
|
|
||||||
idx + old_len + i, (final_markup, new[old_len + i])
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.__class__.assemble_bits(other_map)
|
|
||||||
|
|
||||||
def find_all(self, sub: str):
|
|
||||||
indexes = list()
|
|
||||||
start = 0
|
|
||||||
while True:
|
|
||||||
start = self.plain.find(sub, start)
|
|
||||||
if start == -1:
|
|
||||||
return indexes
|
|
||||||
indexes.append(start)
|
|
||||||
start += len(sub)
|
|
||||||
|
|
||||||
def scramble(self):
|
|
||||||
idx = self.disassemble_bits()
|
|
||||||
random.shuffle(idx)
|
|
||||||
return self.__class__.assemble_bits(idx)
|
|
||||||
|
|
||||||
def reverse(self):
|
|
||||||
idx = self.disassemble_bits()
|
|
||||||
idx.reverse()
|
|
||||||
return self.__class__.assemble_bits(idx)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def assemble_bits(cls, idx: List[Tuple[Optional[Union[str, MudStyle, None]], str]]):
|
|
||||||
out = cls()
|
|
||||||
for i, t in enumerate(idx):
|
|
||||||
s = [Span(0, 1, t[0])]
|
|
||||||
out.append_text(cls(text=t[1], spans=s))
|
|
||||||
return out
|
|
||||||
|
|
||||||
def style_at_index(self, offset: int) -> MudStyle:
|
|
||||||
if offset < 0:
|
|
||||||
offset = len(self) + offset
|
|
||||||
style = MudStyle.null()
|
|
||||||
for start, end, span_style in self._spans:
|
|
||||||
if end > offset >= start:
|
|
||||||
style = style + span_style
|
|
||||||
return style
|
|
||||||
|
|
||||||
def disassemble_bits(self) -> List[Tuple[Optional[Union[str, MudStyle, None]], str]]:
|
|
||||||
idx = list()
|
|
||||||
for i, c in enumerate(self.plain):
|
|
||||||
idx.append((self.style_at_index(i), c))
|
|
||||||
return idx
|
|
||||||
|
|
||||||
def squish(self) -> "MudText":
|
|
||||||
"""
|
|
||||||
Removes leading and trailing whitespace, and coerces all internal whitespace sequences
|
|
||||||
into at most a single space. Returns the results.
|
|
||||||
"""
|
|
||||||
out = list()
|
|
||||||
matches = _RE_SQUISH.finditer(self.plain)
|
|
||||||
for match in matches:
|
|
||||||
out.append(self[match.start(): match.end()])
|
|
||||||
return self.__class__(" ").join(out)
|
|
||||||
|
|
||||||
def squish_spaces(self) -> "MudText":
|
|
||||||
"""
|
|
||||||
Like squish, but retains newlines and tabs. Just squishes spaces.
|
|
||||||
"""
|
|
||||||
out = list()
|
|
||||||
matches = _RE_NOTSPACE.finditer(self.plain)
|
|
||||||
for match in matches:
|
|
||||||
out.append(self[match.start(): match.end()])
|
|
||||||
return self.__class__(" ").join(out)
|
|
||||||
|
|
||||||
def serialize(self) -> dict:
|
|
||||||
def ser_style(style):
|
|
||||||
if isinstance(style, str):
|
|
||||||
style = MudStyle.parse(style)
|
|
||||||
if not isinstance(style, MudStyle):
|
|
||||||
style = MudStyle.upgrade(style)
|
|
||||||
return style.serialize()
|
|
||||||
|
|
||||||
def ser_span(span):
|
|
||||||
if not span.style:
|
|
||||||
return None
|
|
||||||
return {
|
|
||||||
"start": span.start,
|
|
||||||
"end": span.end,
|
|
||||||
"style": ser_style(span.style),
|
|
||||||
}
|
|
||||||
|
|
||||||
out = {"text": self.plain}
|
|
||||||
|
|
||||||
if self.style:
|
|
||||||
out["style"] = ser_style(self.style)
|
|
||||||
|
|
||||||
out_spans = [s for span in self.spans if (s := ser_span(span))]
|
|
||||||
|
|
||||||
if out_spans:
|
|
||||||
out["spans"] = out_spans
|
|
||||||
|
|
||||||
return out
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def deserialize(cls, data) -> "Text":
|
|
||||||
text = data.get("text", None)
|
|
||||||
if text is None:
|
|
||||||
return cls("")
|
|
||||||
style = data.get("style", None)
|
|
||||||
if style:
|
|
||||||
style = MudStyle(**style)
|
|
||||||
|
|
||||||
spans = data.get("spans", None)
|
|
||||||
|
|
||||||
if spans:
|
|
||||||
spans = [Span(s["start"], s["end"], MudStyle(**s["style"])) for s in spans]
|
|
||||||
|
|
||||||
return cls(text=text, style=style, spans=spans)
|
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_STYLES = dict()
|
|
||||||
|
|
||||||
|
|
||||||
def install():
|
|
||||||
from rich import style, text, console, default_styles, themes, syntax, traceback
|
|
||||||
global DEFAULT_STYLES
|
|
||||||
style.Style = MudStyle
|
|
||||||
style.NULL_STYLE = MudStyle()
|
|
||||||
text.Text = MudText
|
|
||||||
console.Console = MudConsole
|
|
||||||
console.ConsoleOptions = MudConsoleOptions
|
|
||||||
|
|
||||||
traceback.Style = MudStyle
|
|
||||||
syntax.Style = MudStyle
|
|
||||||
traceback.Text = MudText
|
|
||||||
syntax.Text = MudText
|
|
||||||
|
|
||||||
for k, v in default_styles.DEFAULT_STYLES.items():
|
|
||||||
DEFAULT_STYLES[k] = MudStyle.upgrade(v)
|
|
||||||
|
|
||||||
for theme in syntax.RICH_SYNTAX_THEMES.values():
|
|
||||||
for k, v in theme.items():
|
|
||||||
if isinstance(v, OLD_STYLE):
|
|
||||||
theme[k] = MudStyle.upgrade(v)
|
|
||||||
|
|
||||||
default_styles.DEFAULT_STYLES = DEFAULT_STYLES
|
|
||||||
themes.DEFAULT = themes.Theme(DEFAULT_STYLES)
|
|
||||||
|
|
@ -83,8 +83,7 @@ dependencies = [
|
||||||
"anything ==0.2.1",
|
"anything ==0.2.1",
|
||||||
"black >= 22.6",
|
"black >= 22.6",
|
||||||
"isort >= 5.10",
|
"isort >= 5.10",
|
||||||
"parameterized ==0.8.1",
|
"parameterized ==0.8.1"
|
||||||
"rich >= 13.3.5,
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue