aboutsummaryrefslogtreecommitdiffstats
path: root/bot/utils/__init__.py
diff options
context:
space:
mode:
Diffstat (limited to 'bot/utils/__init__.py')
-rw-r--r--bot/utils/__init__.py74
1 files changed, 62 insertions, 12 deletions
diff --git a/bot/utils/__init__.py b/bot/utils/__init__.py
index ef18a1b9..0aa50af6 100644
--- a/bot/utils/__init__.py
+++ b/bot/utils/__init__.py
@@ -1,4 +1,6 @@
import asyncio
+import re
+import string
from typing import List
import discord
@@ -9,21 +11,15 @@ from bot.pagination import LinePaginator
async def disambiguate(
ctx: Context, entries: List[str], *, timeout: float = 30,
- per_page: int = 20, empty: bool = False, embed: discord.Embed = None
-):
+ entries_per_page: int = 20, empty: bool = False, embed: discord.Embed = None
+) -> str:
"""
Has the user choose between multiple entries in case one could not be chosen automatically.
+ Disambiguation will be canceled after `timeout` seconds.
+
This will raise a BadArgument if entries is empty, if the disambiguation event times out,
or if the user makes an invalid choice.
-
- :param ctx: Context object from discord.py
- :param entries: List of items for user to choose from
- :param timeout: Number of seconds to wait before canceling disambiguation
- :param per_page: Entries per embed page
- :param empty: Whether the paginator should have an extra line between items
- :param embed: The embed that the paginator will use.
- :return: Users choice for correct entry.
"""
if len(entries) == 0:
raise BadArgument('No matches found.')
@@ -33,7 +29,7 @@ async def disambiguate(
choices = (f'{index}: {entry}' for index, entry in enumerate(entries, start=1))
- def check(message):
+ def check(message: discord.Message) -> bool:
return (message.content.isdigit()
and message.author == ctx.author
and message.channel == ctx.channel)
@@ -43,7 +39,7 @@ async def disambiguate(
embed = discord.Embed()
coro1 = ctx.bot.wait_for('message', check=check, timeout=timeout)
- coro2 = LinePaginator.paginate(choices, ctx, embed=embed, max_lines=per_page,
+ coro2 = LinePaginator.paginate(choices, ctx, embed=embed, max_lines=entries_per_page,
empty=empty, max_size=6000, timeout=9000)
# wait_for timeout will go to except instead of the wait_for thing as I expected
@@ -77,3 +73,57 @@ async def disambiguate(
return entries[index - 1]
except IndexError:
raise BadArgument('Invalid choice.')
+
+
+def replace_many(
+ sentence: str, replacements: dict, *, ignore_case: bool = False, match_case: bool = False
+) -> str:
+ """
+ Replaces multiple substrings in a string given a mapping of strings.
+
+ By default replaces long strings before short strings, and lowercase before uppercase.
+ Example:
+ var = replace_many("This is a sentence", {"is": "was", "This": "That"})
+ assert var == "That was a sentence"
+
+ If `ignore_case` is given, does a case insensitive match.
+ Example:
+ var = replace_many("THIS is a sentence", {"IS": "was", "tHiS": "That"}, ignore_case=True)
+ assert var == "That was a sentence"
+
+ If `match_case` is given, matches the case of the replacement with the replaced word.
+ Example:
+ var = replace_many(
+ "This IS a sentence", {"is": "was", "this": "that"}, ignore_case=True, match_case=True
+ )
+ assert var == "That WAS a sentence"
+ """
+ if ignore_case:
+ replacements = dict(
+ (word.lower(), replacement) for word, replacement in replacements.items()
+ )
+
+ words_to_replace = sorted(replacements, key=lambda s: (-len(s), s))
+
+ # Join and compile words to replace into a regex
+ pattern = "|".join(re.escape(word) for word in words_to_replace)
+ regex = re.compile(pattern, re.I if ignore_case else 0)
+
+ def _repl(match: re.Match) -> str:
+ """Returns replacement depending on `ignore_case` and `match_case`."""
+ word = match.group(0)
+ replacement = replacements[word.lower() if ignore_case else word]
+
+ if not match_case:
+ return replacement
+
+ # Clean punctuation from word so string methods work
+ cleaned_word = word.translate(str.maketrans('', '', string.punctuation))
+ if cleaned_word.isupper():
+ return replacement.upper()
+ elif cleaned_word[0].isupper():
+ return replacement.capitalize()
+ else:
+ return replacement.lower()
+
+ return regex.sub(_repl, sentence)