Source code for mcstatus.motd.transformers
from __future__ import annotations
import abc
import typing as t
from collections.abc import Callable, Sequence
from mcstatus.motd.components import Formatting, MinecraftColor, ParsedMotdComponent, TranslationTag, WebColor
_HOOK_RETURN_TYPE = t.TypeVar("_HOOK_RETURN_TYPE")
_END_RESULT_TYPE = t.TypeVar("_END_RESULT_TYPE")
[docs]class BaseTransformer(abc.ABC, t.Generic[_HOOK_RETURN_TYPE, _END_RESULT_TYPE]):
"""Base motd transformer class.
Motd transformer is responsible for providing a way to generate an alternative representation
of motd, such as one that is able to be printed in the terminal.
"""
[docs] def transform(self, motd_components: Sequence[ParsedMotdComponent]) -> _END_RESULT_TYPE:
return self._format_output([handled for component in motd_components for handled in self._handle_component(component)])
[docs] @abc.abstractmethod
def _format_output(self, results: list[_HOOK_RETURN_TYPE]) -> _END_RESULT_TYPE: ...
[docs] def _handle_component(
self, component: ParsedMotdComponent
) -> tuple[_HOOK_RETURN_TYPE, _HOOK_RETURN_TYPE] | tuple[_HOOK_RETURN_TYPE]:
handler: Callable[[ParsedMotdComponent], _HOOK_RETURN_TYPE] = {
MinecraftColor: self._handle_minecraft_color,
WebColor: self._handle_web_color,
Formatting: self._handle_formatting,
TranslationTag: self._handle_translation_tag,
str: self._handle_str,
}[type(component)]
additional = None
if isinstance(component, MinecraftColor):
additional = self._handle_formatting(Formatting.RESET)
return (additional, handler(component)) if additional is not None else (handler(component),)
[docs] @abc.abstractmethod
def _handle_translation_tag(self, _: TranslationTag, /) -> _HOOK_RETURN_TYPE: ...
[docs] @abc.abstractmethod
def _handle_web_color(self, element: WebColor, /) -> _HOOK_RETURN_TYPE: ...
[docs] @abc.abstractmethod
def _handle_formatting(self, element: Formatting, /) -> _HOOK_RETURN_TYPE: ...
[docs] @abc.abstractmethod
def _handle_minecraft_color(self, element: MinecraftColor, /) -> _HOOK_RETURN_TYPE: ...
[docs]class NothingTransformer(BaseTransformer[str, str]):
"""Transformer that transforms all elements into empty strings.
This transformer acts as a base for other transformers with string result type.
"""
[docs]class MinecraftTransformer(PlainTransformer):
[docs] def _handle_component(self, component: ParsedMotdComponent) -> tuple[str, str] | tuple[str]:
result = super()._handle_component(component)
if len(result) == 2:
return (result[1],)
return result
[docs] def _handle_minecraft_color(self, element: MinecraftColor, /) -> str:
return "§" + element.value
[docs]class HtmlTransformer(PlainTransformer):
"""Formatter for HTML variant of a MOTD.
.. warning::
You should implement obfuscated CSS class yourself (name - ``obfuscated``).
See `this answer <https://stackoverflow.com/a/30313558>`_ as example.
"""
FORMATTING_TO_HTML_TAGS = {
Formatting.BOLD: "b",
Formatting.STRIKETHROUGH: "s",
Formatting.ITALIC: "i",
Formatting.UNDERLINED: "u",
}
MINECRAFT_COLOR_TO_RGB_BEDROCK = {
MinecraftColor.BLACK: ((0, 0, 0), (0, 0, 0)),
MinecraftColor.DARK_BLUE: ((0, 0, 170), (0, 0, 42)),
MinecraftColor.DARK_GREEN: ((0, 170, 0), (0, 42, 0)),
MinecraftColor.DARK_AQUA: ((0, 170, 170), (0, 42, 42)),
MinecraftColor.DARK_RED: ((170, 0, 0), (42, 0, 0)),
MinecraftColor.DARK_PURPLE: ((170, 0, 170), (42, 0, 42)),
MinecraftColor.GOLD: ((255, 170, 0), (64, 42, 0)),
MinecraftColor.GRAY: ((170, 170, 170), (42, 42, 42)),
MinecraftColor.DARK_GRAY: ((85, 85, 85), (21, 21, 21)),
MinecraftColor.BLUE: ((85, 85, 255), (21, 21, 63)),
MinecraftColor.GREEN: ((85, 255, 85), (21, 63, 21)),
MinecraftColor.AQUA: ((85, 255, 255), (21, 63, 63)),
MinecraftColor.RED: ((255, 85, 85), (63, 21, 21)),
MinecraftColor.LIGHT_PURPLE: ((255, 85, 255), (63, 21, 63)),
MinecraftColor.YELLOW: ((255, 255, 85), (63, 63, 21)),
MinecraftColor.WHITE: ((255, 255, 255), (63, 63, 63)),
MinecraftColor.MINECOIN_GOLD: ((221, 214, 5), (55, 53, 1)),
}
MINECRAFT_COLOR_TO_RGB_JAVA = MINECRAFT_COLOR_TO_RGB_BEDROCK.copy()
MINECRAFT_COLOR_TO_RGB_JAVA[MinecraftColor.GOLD] = ((255, 170, 0), (42, 42, 0))
def __init__(self, *, bedrock: bool = False) -> None:
self.bedrock = bedrock
self.on_reset = []
[docs] def transform(self, motd_components: Sequence[ParsedMotdComponent]) -> str:
self.on_reset = []
return super().transform(motd_components)
[docs] def _format_output(self, results: list[str]) -> str:
return "<p>" + super()._format_output(results) + "".join(self.on_reset) + "</p>"
[docs] def _handle_minecraft_color(self, element: MinecraftColor, /) -> str:
color_map = self.MINECRAFT_COLOR_TO_RGB_BEDROCK if self.bedrock else self.MINECRAFT_COLOR_TO_RGB_JAVA
fg_color, bg_color = color_map[element]
self.on_reset.append("</span>")
return f"<span style='color:rgb{fg_color};text-shadow:0 0 1px rgb{bg_color}'>"
[docs] def _handle_web_color(self, element: WebColor, /) -> str:
self.on_reset.append("</span>")
return f"<span style='color:rgb{element.rgb}'>"
[docs] def _handle_formatting(self, element: Formatting, /) -> str:
if element is Formatting.RESET:
to_return = "".join(self.on_reset)
self.on_reset = []
return to_return
if element is Formatting.OBFUSCATED:
self.on_reset.append("</span>")
return "<span class=obfuscated>"
tag_name = self.FORMATTING_TO_HTML_TAGS[element]
self.on_reset.append(f"</{tag_name}>")
return f"<{tag_name}>"
[docs]class AnsiTransformer(PlainTransformer):
FORMATTING_TO_ANSI_TAGS = {
Formatting.BOLD: "1",
Formatting.STRIKETHROUGH: "9",
Formatting.ITALIC: "3",
Formatting.UNDERLINED: "4",
Formatting.OBFUSCATED: "5",
}
MINECRAFT_COLOR_TO_RGB = {
MinecraftColor.BLACK: (0, 0, 0),
MinecraftColor.DARK_BLUE: (0, 0, 170),
MinecraftColor.DARK_GREEN: (0, 170, 0),
MinecraftColor.DARK_AQUA: (0, 170, 170),
MinecraftColor.DARK_RED: (170, 0, 0),
MinecraftColor.DARK_PURPLE: (170, 0, 170),
MinecraftColor.GOLD: (255, 170, 0),
MinecraftColor.GRAY: (170, 170, 170),
MinecraftColor.DARK_GRAY: (85, 85, 85),
MinecraftColor.BLUE: (85, 85, 255),
MinecraftColor.GREEN: (85, 255, 85),
MinecraftColor.AQUA: (85, 255, 255),
MinecraftColor.RED: (255, 85, 85),
MinecraftColor.LIGHT_PURPLE: (255, 85, 255),
MinecraftColor.YELLOW: (255, 255, 85),
MinecraftColor.WHITE: (255, 255, 255),
MinecraftColor.MINECOIN_GOLD: (221, 214, 5),
}
[docs] def ansi_color(self, color: tuple[int, int, int] | MinecraftColor) -> str:
"""Transform RGB color to ANSI color code."""
if isinstance(color, MinecraftColor):
color = self.MINECRAFT_COLOR_TO_RGB[color]
return "\033[38;2;{0};{1};{2}m".format(*color)
[docs] def _format_output(self, results: list[str]) -> str:
return "\033[0m" + super()._format_output(results) + "\033[0m"
[docs] def _handle_minecraft_color(self, element: MinecraftColor, /) -> str:
return self.ansi_color(element)
[docs] def _handle_web_color(self, element: WebColor, /) -> str:
return self.ansi_color(element.rgb)
[docs] def _handle_formatting(self, element: Formatting, /) -> str:
if element is Formatting.RESET:
return "\033[0m"
return "\033[" + self.FORMATTING_TO_ANSI_TAGS[element] + "m"