194 lines
6.7 KiB
Python
194 lines
6.7 KiB
Python
from __future__ import annotations
|
|
|
|
import typing as t
|
|
from collections.abc import Sequence
|
|
|
|
from mcstatus.motd.components import Formatting, MinecraftColor, ParsedMotdComponent, WebColor
|
|
|
|
_PARSED_MOTD_COMPONENTS_TYPEVAR = t.TypeVar("_PARSED_MOTD_COMPONENTS_TYPEVAR", bound="list[ParsedMotdComponent]")
|
|
|
|
|
|
def get_unused_elements(parsed: Sequence[ParsedMotdComponent]) -> set[int]:
|
|
"""Get indices of all items which are unused and can be safely removed from the MOTD.
|
|
|
|
This is a wrapper method around several unused item collection methods.
|
|
"""
|
|
to_remove: set[int] = set()
|
|
|
|
for simplifier in [
|
|
get_double_items,
|
|
get_double_colors,
|
|
get_formatting_before_color,
|
|
get_meaningless_resets_and_colors,
|
|
get_empty_text,
|
|
get_end_non_text,
|
|
]:
|
|
to_remove.update(simplifier(parsed))
|
|
|
|
return to_remove
|
|
|
|
|
|
def squash_nearby_strings(parsed: _PARSED_MOTD_COMPONENTS_TYPEVAR) -> _PARSED_MOTD_COMPONENTS_TYPEVAR:
|
|
"""Squash duplicate strings together.
|
|
|
|
Note that this function doesn't create a copy of passed array, it modifies it.
|
|
This is what those typevars are for in the function signature.
|
|
"""
|
|
# in order to not break indexes, we need to fill values and then remove them after the loop
|
|
fillers: set[int] = set()
|
|
for index, item in enumerate(parsed):
|
|
if not isinstance(item, str):
|
|
continue
|
|
|
|
try:
|
|
next_item = parsed[index + 1]
|
|
except IndexError: # Last item (without any next item)
|
|
break
|
|
|
|
if isinstance(next_item, str):
|
|
parsed[index + 1] = item + next_item
|
|
fillers.add(index)
|
|
|
|
for already_removed, index_to_remove in enumerate(fillers):
|
|
parsed.pop(index_to_remove - already_removed)
|
|
|
|
return parsed
|
|
|
|
|
|
def get_double_items(parsed: Sequence[ParsedMotdComponent]) -> set[int]:
|
|
"""Get indices of all doubled items that can be removed.
|
|
|
|
Removes any items that are followed by an item of the same kind (compared using ``__eq__``).
|
|
"""
|
|
to_remove: set[int] = set()
|
|
|
|
for index, item in enumerate(parsed):
|
|
try:
|
|
next_item = parsed[index + 1]
|
|
except IndexError: # Last item (without any next item)
|
|
break
|
|
|
|
if isinstance(item, (Formatting, MinecraftColor, WebColor)) and item == next_item:
|
|
to_remove.add(index)
|
|
|
|
return to_remove
|
|
|
|
|
|
def get_double_colors(parsed: Sequence[ParsedMotdComponent]) -> set[int]:
|
|
"""Get indices of all doubled color items.
|
|
|
|
As colors (obviously) override each other, we only ever care about the last one, ignore
|
|
the previous ones. (for example: specifying red color, then orange, then yellow, then some text
|
|
will just result in yellow text)
|
|
"""
|
|
to_remove: set[int] = set()
|
|
|
|
prev_color: int | None = None
|
|
for index, item in enumerate(parsed):
|
|
if isinstance(item, (MinecraftColor, WebColor)):
|
|
# If we found a color after another, remove the previous color
|
|
if prev_color is not None:
|
|
to_remove.add(prev_color)
|
|
prev_color = index
|
|
|
|
# If we find a string, that's what our color we found previously applies to,
|
|
# set prev_color to None, marking this color as used
|
|
if isinstance(item, str):
|
|
prev_color = None
|
|
|
|
return to_remove
|
|
|
|
|
|
def get_formatting_before_color(parsed: Sequence[ParsedMotdComponent]) -> set[int]:
|
|
"""Obtain indices of all unused formatting items before colors.
|
|
|
|
Colors override any formatting before them, meaning we only ever care about the color, and can
|
|
ignore all formatting before it. (For example: specifying bold formatting, then italic, then yellow,
|
|
will just result in yellow text.)
|
|
"""
|
|
to_remove: set[int] = set()
|
|
|
|
collected_formattings = []
|
|
for index, item in enumerate(parsed):
|
|
# Collect the indices of formatting items
|
|
if isinstance(item, Formatting):
|
|
collected_formattings.append(index)
|
|
|
|
# Only run checks if we have some collected formatting items
|
|
if len(collected_formattings) == 0:
|
|
continue
|
|
|
|
# If there's a string after some formattings, the formattings apply to it.
|
|
# This means they're not unused, remove them.
|
|
if isinstance(item, str) and not item.isspace():
|
|
collected_formattings = []
|
|
continue
|
|
|
|
# If there's a color after some formattings, these formattings will be overridden
|
|
# as colors reset everything. This makes these formattings pointless, mark them
|
|
# for removal.
|
|
if isinstance(item, (MinecraftColor, WebColor)):
|
|
to_remove.update(collected_formattings)
|
|
collected_formattings = []
|
|
return to_remove
|
|
|
|
|
|
def get_empty_text(parsed: Sequence[ParsedMotdComponent]) -> set[int]:
|
|
"""Get indices of all empty text items.
|
|
|
|
Empty strings in motd serve no purpose and can be marked for removal.
|
|
"""
|
|
to_remove: set[int] = set()
|
|
|
|
for index, item in enumerate(parsed):
|
|
if isinstance(item, str) and len(item) == 0:
|
|
to_remove.add(index)
|
|
|
|
return to_remove
|
|
|
|
|
|
def get_end_non_text(parsed: Sequence[ParsedMotdComponent]) -> set[int]:
|
|
"""Get indices of all trailing items, found after the last text component.
|
|
|
|
Any color/formatting items only make sense when they apply to some text.
|
|
If there are some at the end, after the last text, they're pointless and
|
|
can be removed.
|
|
"""
|
|
to_remove: set[int] = set()
|
|
|
|
for rev_index, item in enumerate(reversed(parsed)):
|
|
# The moment we find our last string, stop the loop
|
|
if isinstance(item, str):
|
|
break
|
|
|
|
# Remove any color/formatting that doesn't apply to text
|
|
if isinstance(item, (MinecraftColor, WebColor, Formatting)):
|
|
index = len(parsed) - 1 - rev_index
|
|
to_remove.add(index)
|
|
|
|
return to_remove
|
|
|
|
|
|
def get_meaningless_resets_and_colors(parsed: Sequence[ParsedMotdComponent]) -> set[int]:
|
|
to_remove: set[int] = set()
|
|
|
|
active_color: MinecraftColor | WebColor | None = None
|
|
active_formatting: Formatting | None = None
|
|
for index, item in enumerate(parsed):
|
|
if isinstance(item, (MinecraftColor, WebColor)):
|
|
if active_color == item:
|
|
to_remove.add(index)
|
|
active_color = item
|
|
continue
|
|
if isinstance(item, Formatting):
|
|
if item == Formatting.RESET:
|
|
if active_color is None and active_formatting is None:
|
|
to_remove.add(index)
|
|
continue
|
|
active_color, active_formatting = None, None
|
|
continue
|
|
if active_formatting == item:
|
|
to_remove.add(index)
|
|
active_formatting = item
|
|
|
|
return to_remove
|