mcdreforged.minecraft.rtext.text 源代码

import json
from abc import ABC
from typing import Iterable, List, Union, Optional, Any, Tuple, Set, NamedTuple

from colorama import Style
from typing_extensions import Self

from mcdreforged.minecraft.rtext.style import RStyle, RColor, RAction, RColorClassic, RColorRGB, RItemClassic
from mcdreforged.utils import class_util


[文档] class RTextBase(ABC): """ An abstract base class of Minecraft text component """
[文档] def to_json_object(self) -> Union[dict, list]: """ Return an object representing its data that can be serialized into a json string """ raise NotImplementedError()
[文档] def to_json_str(self) -> str: """ Return a json formatted str representing its data It can be used as the second parameter in Minecraft command ``/tellraw <target> <message>`` and more """ return json.dumps(self.to_json_object(), ensure_ascii=False, separators=(',', ':'))
[文档] def to_plain_text(self) -> str: """ Return a plain text for console display Click event and hover event will be ignored """ raise NotImplementedError()
[文档] def to_colored_text(self) -> str: """ Return a colored text stained with ANSI escape code for console display Click event and hover event will be ignored """ raise NotImplementedError()
[文档] def to_legacy_text(self) -> str: """ Return a colored text stained with classic "§"-prefixed Minecraft formatting code Click event and hover event will be ignored """ raise NotImplementedError()
[文档] def copy(self) -> Self: """ Return a deep copy version of itself """ raise NotImplementedError()
[文档] def set_color(self, color: RColor) -> Self: """ Set the color of the text and return the text component itself """ raise NotImplementedError()
[文档] def set_styles(self, styles: Union[RStyle, Iterable[RStyle]]) -> Self: """ Set the styles of the text and return the text component itself """ raise NotImplementedError()
[文档] def set_click_event(self, action: RAction, value: str) -> Self: """ Set the click event Method :meth:`c` is the short form of :meth:`set_click_event` :param action: The type of the action :param value: The string value of the action :return: The text component itself """ raise NotImplementedError()
[文档] def set_hover_text(self, *args) -> Self: """ Set the hover text Method :meth:`h` is the short form of :meth:`set_hover_text` :param args: The elements be used to create a :class:`RTextList` instance. The :class:`RTextList` instance is used as the actual hover text :return: The text component itself """ raise NotImplementedError()
[文档] def c(self, action: RAction, value: str) -> Self: """ The short form of :meth:`set_click_event` """ return self.set_click_event(action, value)
[文档] def h(self, *args) -> Self: """ The short form of :meth:`set_hover_text` """ return self.set_hover_text(*args)
def __str__(self): return self.to_plain_text() def __add__(self, other): return RTextList(self, other) def __radd__(self, other): return RTextList(other, self)
[文档] @staticmethod def from_any(text) -> 'RTextBase': """ Convert anything into a RText component """ if isinstance(text, RTextBase): return text return RText(text)
[文档] @staticmethod def join(divider: Any, iterable: Iterable[Any]) -> 'RTextBase': """ Just like method :meth:`str.join`, it concatenates any number of texts with *divider* Example:: >>> text = RTextBase.join(',', [RText('1'), '2', 3]) >>> text.to_plain_text() '1,2,3' :param divider: The divider between elements. The divider object will be reused :param iterable: The elements to be joined """ result = RTextList() for i, item in enumerate(iterable): if i > 0: result.append(divider) result.append(item) return result
[文档] @staticmethod def format(fmt: str, *args, **kwargs) -> 'RTextBase': """ Just like method :meth:`str.format`, it uses *\\*args* and *\\*\\*kwargs* to build a formatted RText component based on the formatter *fmt* Example:: >>> text = RTextBase.format('a={},b={},c={c}', RText('1', color=RColor.blue), '2', c=3) >>> text.to_plain_text() 'a=1,b=2,c=3' :param fmt: The formatter string :param args: The given arguments :param kwargs: The given keyword arguments """ args = list(args) kwargs = kwargs.copy() counter = 0 rtext_elements = [] # type: List[Tuple[str, RTextBase]] def get(): nonlocal counter rv = '@@MCDR#RText.format#Placeholder#{}@@'.format(counter) counter += 1 return rv for i, arg in enumerate(args): if isinstance(arg, RTextBase): placeholder = get() rtext_elements.append((placeholder, arg)) args[i] = placeholder for key, value in kwargs.items(): if isinstance(value, RTextBase): placeholder = get() rtext_elements.append((placeholder, value)) kwargs[key] = placeholder texts = [fmt.format(*args, **kwargs)] for placeholder, rtext in rtext_elements: new_texts = [] for text in texts: processed_text = [] if isinstance(text, str): for j, ele in enumerate(text.split(placeholder)): if j > 0: processed_text.append(rtext) processed_text.append(ele) else: processed_text.append(text) new_texts.extend(processed_text) texts = new_texts return RTextList(*texts)
[文档] @classmethod def from_json_object(cls, data: Union[str, list, dict]) -> 'RTextBase': """ Convert a json object into a :class:`RText <RTextBase>` component Example:: >>> text = RTextBase.from_json_object({'text': 'my text', 'color': 'red'}) >>> text.to_plain_text() 'my text' >>> text.to_json_object()['color'] 'red' :param data: A json object .. versionadded:: v2.4.0 """ if isinstance(data, str): return cls.from_any(data) if isinstance(data, list): if len(data) == 0: raise ValueError('Empty list') lst = list(map(cls.from_json_object, data)) text = RTextList() if data[0] != '': text.set_header_text(lst[0]) text.append(*lst[1:]) return text elif isinstance(data, dict): if 'text' in data: text = RText(data['text']) elif 'translate' in data: if 'with' in data: args = data['with'] else: args = [] text = RTextTranslation(data['translate']).arg(*map(cls.from_json_object, args)) else: raise ValueError('No method to create RText from {}'.format(data)) if 'extra' in data: siblings = data['extra'] if isinstance(siblings, list): text_list = RTextList() text_list.set_header_text(text) text_list.append(*map(cls.from_json_object, siblings)) text = text_list styles = [] for style in RStyle: if data.get(style.name, False): styles.append(style) text.set_styles(styles) if 'color' in data: try: text.set_color(RColor.from_mc_value(data['color'])) except ValueError: pass try: click_event = data['clickEvent'] if isinstance(click_event, dict): text.set_click_event(RAction[click_event['action']], click_event['value']) except KeyError: pass try: hover_event = data['hoverEvent'] if isinstance(hover_event, dict) and hover_event['action'] == 'show_text': text.set_hover_text(cls.from_json_object(hover_event['value'])) except KeyError: pass return text
class _ClickEvent(NamedTuple): action: RAction value: str
[文档] class RText(RTextBase): """ The regular text component class """
[文档] def __init__(self, text, color: Optional[RColor] = None, styles: Optional[Union[RStyle, Iterable[RStyle]]] = None): """ Create a :class:`RText` object with specific text, optional color and optional style :param text: The content of the text. It will be converted into str :param color: Optional, the color of the text :param styles: Optional, the style of the text. It can be a single :class:`~mcdreforged.minecraft.rtext.style.RStyle` or an iterable of :class:`~mcdreforged.minecraft.rtext.style.RStyle` """ self.__text: str = str(text) self.__color: Optional[RColor] = None self.__styles: Set[RStyle] = set() self.__click_event: Optional[_ClickEvent] = None self.__hover_text_list: list = [] if color is not None: self.set_color(color) if styles is not None: self.set_styles(styles)
def set_color(self, color: RColor) -> Self: self.__color = color return self def set_styles(self, styles: Union[RStyle, Iterable[RStyle]]) -> Self: if isinstance(styles, RStyle): styles = {styles} elif isinstance(styles, Iterable): styles = set(styles) else: raise TypeError('Unsupported style type {}'.format(type(styles))) self.__styles = styles return self def set_click_event(self, action: RAction, value: str) -> Self: self.__click_event = _ClickEvent(action, value) return self def set_hover_text(self, *args) -> Self: self.__hover_text_list = list(args) return self def to_json_object(self) -> Union[dict, list]: obj = {'text': self.__text} if self.__color is not None: obj['color'] = self.__color.name for style in self.__styles: obj[style.name] = True if self.__click_event is not None: obj['clickEvent'] = { 'action': self.__click_event.action.name, 'value': self.__click_event.value } if len(self.__hover_text_list) > 0: if len(self.__hover_text_list) == 1: hover_value = RTextBase.from_any(self.__hover_text_list[0]).to_json_object() else: hover_value = { 'text': '', 'extra': RTextList(*self.__hover_text_list).to_json_object(), } obj['hoverEvent'] = { 'action': 'show_text', 'value': hover_value, } return obj def to_plain_text(self) -> str: return self.__text def _get_console_style_codes(self) -> str: if isinstance(self.__color, RColorClassic): color = self.__color.console_code elif isinstance(self.__color, RColorRGB): color = self.__color.to_classic().console_code else: color = '' for style in self.__styles: if isinstance(style, RItemClassic): color += style.console_code return color def to_colored_text(self) -> str: head = self._get_console_style_codes() tail = Style.RESET_ALL if len(head) > 0 else '' return head + self.to_plain_text() + tail def _get_legacy_style_codes(self) -> str: if isinstance(self.__color, RColorClassic): color = self.__color.mc_code elif isinstance(self.__color, RColorRGB): color = self.__color.to_classic().mc_code else: color = '' for style in self.__styles: if isinstance(style, RItemClassic): color += style.mc_code return color def to_legacy_text(self) -> str: head = self._get_legacy_style_codes() tail = RColor.reset.mc_code if len(head) > 0 else '' return head + self.to_plain_text() + tail def _copy_from(self, text: 'RText'): self.__text = text.__text self.__color = text.__color self.__styles = text.__styles.copy() self.__click_event = text.__click_event self.__hover_text_list = text.__hover_text_list.copy() def copy(self) -> 'RText': copied = RText('') copied._copy_from(self) return copied def __repr__(self) -> str: return class_util.represent(self, fields={ 'text': self.__text, 'color': self.__color, 'styles': self.__styles, 'click_event': self.__click_event, 'hover_texts': self.__hover_text_list })
[文档] class RTextList(RTextBase): """ A list of :class:`RTextBase` objects, a compound text component """
[文档] def __init__(self, *args): """ Use the given *\\*args* to create a :class:`RTextList` :param args: The items in this :class:`RTextList`. They can be :class:`str`, :class:`RTextBase` or any class implements ``__str__`` method. All non- :class:`RTextBase` items will be converted to :class:`RText` """ self.header = RText('') self.header_empty = True self.children = [] # type: List[RTextBase] self.append(*args)
def set_color(self, color: RColor) -> Self: self.header.set_color(color) self.header_empty = False return self def set_styles(self, styles: Union[RStyle, Iterable[RStyle]]) -> Self: self.header.set_styles(styles) self.header_empty = False return self def set_click_event(self, action: RAction, value) -> Self: self.header.set_click_event(action, value) self.header_empty = False return self def set_hover_text(self, *args) -> Self: self.header.set_hover_text(*args) self.header_empty = False return self def set_header_text(self, header_text: RTextBase) -> Self: self.header = header_text self.header_empty = False return self def append(self, *args) -> Self: for obj in args: self.children.append(RTextBase.from_any(obj)) return self def is_empty(self) -> bool: return len(self.children) == 0 def to_json_object(self) -> Union[dict, list]: ret = ['' if self.header_empty else self.header.to_json_object()] ret.extend(map(lambda rtext: rtext.to_json_object(), self.children)) return ret def to_plain_text(self) -> str: return ''.join(map(lambda rtext: rtext.to_plain_text(), self.children)) def to_colored_text(self) -> str: # noinspection PyProtectedMember head = self.header._get_console_style_codes() tail = Style.RESET_ALL if len(head) > 0 else '' return ''.join(map(lambda rtext: ''.join([head, rtext.to_colored_text(), tail]), self.children)) def to_legacy_text(self) -> str: # noinspection PyProtectedMember head = self.header._get_legacy_style_codes() tail = RColor.reset.mc_code if len(head) > 0 else '' return ''.join(map(lambda rtext: ''.join([head, rtext.to_legacy_text(), tail]), self.children)) def copy(self) -> 'RTextList': copied = RTextList() copied.header = self.header.copy() copied.header_empty = self.header_empty copied.children = [child.copy() for child in self.children] return copied def __repr__(self) -> str: return class_util.represent(self, { 'header': None if self.header_empty else self.header, 'children': self.children })
[文档] class RTextTranslation(RText): """ The translation text component class. It's almost the same as :class:`RText` """
[文档] def __init__(self, translation_key: str, color: RColor = RColor.reset, styles: Optional[Union[RStyle, Iterable[RStyle]]] = None): """ Create a :class:`RTextTranslation` object with specific translation key. The rest of the parameters are the same to the constructor of :class:`RText` Use method :meth:`arg` to set the translation arguments, if the translation requires some arguments Example:: RTextTranslation('advancements.nether.root.title', color=RColor.red) :param translation_key: The translation key :param color: Optional, the color of the text :param styles: Optional, the style of the text. It can be a single :class:`~mcdreforged.minecraft.rtext.style.RStyle` or a collection of :class:`~mcdreforged.minecraft.rtext.style.RStyle` """ super().__init__(translation_key, color, styles) self.__translation_key: str = translation_key self.__args: tuple = () self.__fallback: Optional[str] = None
[文档] def arg(self, *args: Any) -> Self: """ Set the translation arguments :param args: The translation arguments """ self.__args = args return self
[文档] def fallback(self, fallback: str) -> Self: """ Set the translation fallback .. attention:: Works in Minecraft >= 1.19.4 only :param fallback: The fallback text if the translation is unknown """ self.__fallback = fallback return self
def to_plain_text(self) -> str: return self.__translation_key def to_json_object(self) -> Union[dict, list]: obj = super().to_json_object() obj.pop('text') obj['translate'] = self.__translation_key if len(self.__args) > 0: obj['with'] = list(map(lambda arg: arg.to_json_object() if isinstance(arg, RTextBase) else arg, self.__args)) if self.__fallback is not None: obj['fallback'] = self.__fallback return obj def _copy_from(self, text: 'RTextTranslation'): super()._copy_from(text) self.__translation_key = text.__translation_key self.__args = text.__args def copy(self) -> 'RTextTranslation': copied = RTextTranslation('') copied._copy_from(self) return copied def __repr__(self) -> str: return class_util.represent(self, fields={ 'key': self.__translation_key, 'args': self.__args, 'fallback': self.__fallback, 'color': self.__color, 'styles': self.__styles, 'click_event': self.__click_event, 'hover_texts': self.__hover_text_list })