diff --git a/bot/exts/fun/fun.py b/bot/exts/fun/fun.py index 66c4851729..88e3eef0ad 100644 --- a/bot/exts/fun/fun.py +++ b/bot/exts/fun/fun.py @@ -1,6 +1,6 @@ import json import random -from collections.abc import Iterable +from collections.abc import Callable, Iterable from pathlib import Path from typing import Literal @@ -51,6 +51,21 @@ def _get_random_die() -> str: die_name = f"dice_{random.randint(1, 6)}" return getattr(Emojis, die_name) + @staticmethod + async def _clean_text(ctx: Context, text: str, conversion_func: Callable[[str], str]) -> tuple[str, Embed]: + """This groups the clean and convert functions into one so we can reuse this without duplicated code.""" + text = await clean_text_or_reply(ctx, text) + text, embed = await messages.get_text_and_embed(ctx, text) + # Convert embed if it exists + if embed is not None: + embed = messages.convert_embed(conversion_func, embed) + converted_text = conversion_func(text) + converted_text = helpers.suppress_links(converted_text) + # Don't put >>> if only embed present + if converted_text: + converted_text = f">>> {converted_text.lstrip('> ')}" + return converted_text, embed + @commands.command() async def roll(self, ctx: Context, num_rolls: int = 1) -> None: """Outputs a number of random dice emotes (up to 6).""" @@ -68,17 +83,56 @@ def conversion_func(text: str) -> str: return "".join( char.upper() if round(random.random()) else char.lower() for char in text ) - text = await clean_text_or_reply(ctx, text) - text, embed = await messages.get_text_and_embed(ctx, text) - # Convert embed if it exists - if embed is not None: - embed = messages.convert_embed(conversion_func, embed) - converted_text = conversion_func(text) - converted_text = helpers.suppress_links(converted_text) - # Don't put >>> if only embed present - if converted_text: - converted_text = f">>> {converted_text.lstrip('> ')}" - await ctx.send(content=converted_text, embed=embed) + cleaned_text, embed = await self._clean_text(ctx, text, conversion_func) + await ctx.send(content=cleaned_text, embed=embed) + + @commands.command(name="snakecase", aliases=("scase",)) + async def snakecase_command(self, ctx: Context, *, text: str | None) -> None: + """Attempts to convert the provided string to snake_case.""" + def conversion_func(text: str) -> str: + """Converts the provided string to snake_case.""" + text = helpers.neutralise_string(text) + return "_".join( + text + ) + cleaned_text, embed = await self._clean_text(ctx, text, conversion_func) + await ctx.send(content=cleaned_text, embed=embed) + + @commands.command(name="pascalcase", aliases=("pcase", "pascal",)) + async def pascalcase_command(self, ctx: Context, *, text: str | None) -> None: + """Attempts to convert the provided string to PascalCase.""" + def conversion_func(text: str) -> str: + """Converts the provided string to PascalCase.""" + text = helpers.neutralise_string(text) + return "".join( + word.capitalize() for word in text + ) + cleaned_text, embed = await self._clean_text(ctx, text, conversion_func) + await ctx.send(content=cleaned_text, embed=embed) + + @commands.command(name="screamingsnakecase", aliases=("screamsnake", "ssnake", "screamingsnake",)) + async def screamingsnakecase_command(self, ctx: Context, *, text: str | None) -> None: + """Attempts to convert the provided string to SCREAMING_SNAKE_CASE.""" + def conversion_func(text: str) -> str: + """Converts the provided string to SCREAMING_SNAKE_CASE.""" + text = helpers.neutralise_string(text) + return "_".join( + word.upper() for word in text + ) + cleaned_text, embed = await self._clean_text(ctx, text, conversion_func) + await ctx.send(content=cleaned_text, embed=embed) + + @commands.command(name="camelcase", aliases=("ccase", "camel",)) + async def camelcase_command(self, ctx: Context, *, text: str | None) -> None: + """Attempts to convert the provided string to camelCase.""" + def conversion_func(text: str) -> str: + """Converts the provided string to camelCase.""" + text = helpers.neutralise_string(text) + return "".join( + word.capitalize() if i != 0 else word for i, word in enumerate(text) + ) + cleaned_text, embed = await self._clean_text(ctx, text, conversion_func) + await ctx.send(content=cleaned_text, embed=embed) @commands.group(name="caesarcipher", aliases=("caesar", "cc",)) async def caesarcipher_group(self, ctx: Context) -> None: diff --git a/bot/utils/helpers.py b/bot/utils/helpers.py index 74c2ccd040..df71e813d4 100644 --- a/bot/utils/helpers.py +++ b/bot/utils/helpers.py @@ -6,3 +6,26 @@ def suppress_links(message: str) -> str: for link in set(re.findall(r"https?://[^\s]+", message, re.IGNORECASE)): message = message.replace(link, f"<{link}>") return message + +def neutralise_string(txt: str | None) -> list[str] | None: + """Attempts to neutralise all punctuation and cases and returns a string of lowercase words.""" + # Return early if no text provided. + if not txt: + return None + + # Take out punctuation. + txt = re.sub(r"[\W_]", " ", txt) + + words = [] + for word in txt.split(): + if word.isupper(): + words.append(word.lower()) + else: + old_i = 0 + for i, char in enumerate(word): + if char.isupper() and i != 0: + words.append(word[old_i:i].lower()) + old_i = i + words.append(word[old_i:].lower()) + + return words