diff options
author | 2025-03-29 20:14:02 +0000 | |
---|---|---|
committer | 2025-03-29 20:14:02 +0000 | |
commit | 6c90ab7cf9297dd45802ab0cef21bbf62fd481a1 (patch) | |
tree | ffd998ae01eb839560f5420383c940180368b483 | |
parent | Merge pull request #2973 from python-discord/feat/reminder-add-notify (diff) | |
parent | Merge branch 'main' into zen_slicing (diff) |
Zen slicing (#3297)
* Added the possibility of slicing for the zen command
* Fixed off-by-one error, as the end index can be equal to the length of the zen
* Added support for negative signs and replaced re.search by re.match
* Allows for end_index == len(zen_lines). Previously, in that case, end_index % zen_lines would be 0, even though it's the last line
* Allows for slicing without a specified end index (e.g. "1:" will return all lines from the second to the last)
* Update end index display when slicing in the zen command
Co-authored-by: Vivek Ashokkumar <[email protected]>
* Added tests for the zen command
---------
Co-authored-by: Vivek Ashokkumar <[email protected]>
Co-authored-by: ChrisJL <[email protected]>
-rw-r--r-- | bot/exts/utils/utils.py | 39 | ||||
-rw-r--r-- | tests/bot/exts/utils/test_utils.py | 90 |
2 files changed, 116 insertions, 13 deletions
diff --git a/bot/exts/utils/utils.py b/bot/exts/utils/utils.py index 2faf06fee..1bd5c500b 100644 --- a/bot/exts/utils/utils.py +++ b/bot/exts/utils/utils.py @@ -88,16 +88,14 @@ class Utils(Cog): async def zen( self, ctx: Context, - zen_rule_index: int | None, - *, - search_value: str | None = None + search_value: str | None, ) -> None: """ Show the Zen of Python. Without any arguments, the full Zen will be produced. - If zen_rule_index is provided, the line with that index will be produced. - If only a string is provided, the line which matches best will be produced. + If an index or a slice is provided, the corresponding lines will be produced. + Otherwise, the line which matches best will be produced. """ embed = Embed( colour=Colour.og_blurple(), @@ -105,23 +103,38 @@ class Utils(Cog): description=ZEN_OF_PYTHON ) - if zen_rule_index is None and search_value is None: + if search_value is None: embed.title += ", by Tim Peters" await ctx.send(embed=embed) return zen_lines = ZEN_OF_PYTHON.splitlines() - # Prioritize passing the zen rule index - if zen_rule_index is not None: - + # Prioritize checking for an index or slice + match = re.match(r"(-?\d+)(:(-?\d+)?)?", search_value.split(" ")[0]) + if match: upper_bound = len(zen_lines) - 1 lower_bound = -1 * len(zen_lines) - if not (lower_bound <= zen_rule_index <= upper_bound): - raise BadArgument(f"Please provide an index between {lower_bound} and {upper_bound}.") - embed.title += f" (line {zen_rule_index % len(zen_lines)}):" - embed.description = zen_lines[zen_rule_index] + start_index = int(match.group(1)) + + if not match.group(2): + if not (lower_bound <= start_index <= upper_bound): + raise BadArgument(f"Please provide an index between {lower_bound} and {upper_bound}.") + embed.title += f" (line {start_index % len(zen_lines)}):" + embed.description = zen_lines[start_index] + await ctx.send(embed=embed) + return + + end_index= int(match.group(3)) if match.group(3) else len(zen_lines) + + if not ((lower_bound <= start_index <= upper_bound) and (lower_bound <= end_index <= len(zen_lines))): + raise BadArgument(f"Please provide valid indices between {lower_bound} and {upper_bound}.") + if not (start_index % len(zen_lines) < end_index % (len(zen_lines) + 1)): + raise BadArgument("The start index for the slice must be smaller than the end index.") + + embed.title += f" (lines {start_index%len(zen_lines)}-{(end_index-1)%len(zen_lines)}):" + embed.description = "\n".join(zen_lines[start_index:end_index]) await ctx.send(embed=embed) return diff --git a/tests/bot/exts/utils/test_utils.py b/tests/bot/exts/utils/test_utils.py new file mode 100644 index 000000000..5392e3512 --- /dev/null +++ b/tests/bot/exts/utils/test_utils.py @@ -0,0 +1,90 @@ +import unittest + +from discord import Colour, Embed +from discord.ext.commands import BadArgument + +from bot.exts.utils.utils import Utils, ZEN_OF_PYTHON +from tests.helpers import MockBot, MockContext + + +class ZenTests(unittest.IsolatedAsyncioTestCase): + """ Tests for the `!zen` command. """ + + + def setUp(self): + self.bot = MockBot() + self.cog = Utils(self.bot) + self.ctx = MockContext() + + self.zen_list = ZEN_OF_PYTHON.splitlines() + self.template_embed = Embed(colour=Colour.og_blurple(), title="The Zen of Python", description=ZEN_OF_PYTHON) + + + + async def test_zen_without_arguments(self): + """ Tests if the `!zen` command reacts properly to no arguments. """ + self.template_embed.title += ", by Tim Peters" + + + await self.cog.zen.callback(self.cog,self.ctx, search_value = None) + self.ctx.send.assert_called_once_with(embed=self.template_embed) + + async def test_zen_with_valid_index(self): + """ Tests if the `!zen` command reacts properly to a valid index as an argument. """ + expected_results = { + 0: ("The Zen of Python (line 0):", "Beautiful is better than ugly."), + 10: ("The Zen of Python (line 10):", "Unless explicitly silenced."), + 18: ("The Zen of Python (line 18):", "Namespaces are one honking great idea -- let's do more of those!"), + -1: ("The Zen of Python (line 18):", "Namespaces are one honking great idea -- let's do more of those!"), + -10: ("The Zen of Python (line 9):", "Errors should never pass silently."), + -19: ("The Zen of Python (line 0):", "Beautiful is better than ugly.") + + } + + for index, (title, description) in expected_results.items(): + self.template_embed.title = title + self.template_embed.description = description + ctx = MockContext() + with self.subTest(index = index, expected_title=title, expected_description = description): + await self.cog.zen.callback(self.cog, ctx, search_value = str(index)) + ctx.send.assert_called_once_with(embed = self.template_embed) + + + + async def test_zen_with_invalid_index(self): + """ Tests if the `!zen` command reacts properly to an out-of-bounds index as an argument. """ + # Negative index + with self.subTest(index = -20), self.assertRaises(BadArgument): + await self.cog.zen.callback(self.cog, self.ctx, search_value="-20") + + # Positive index + with self.subTest(index = len(ZEN_OF_PYTHON)), self.assertRaises(BadArgument): + await self.cog.zen.callback(self.cog, self.ctx, search_value=str(len(ZEN_OF_PYTHON))) + + async def test_zen_with_valid_slices(self): + """ Tests if the `!zen` command reacts properly to valid slices for indexing as an argument. """ + + expected_results = { + "0:19": ("The Zen of Python (lines 0-18):", "\n".join(self.zen_list[0:19])), + "0:": ("The Zen of Python (lines 0-18):", "\n".join(self.zen_list[0:])), + "-2:-1": ("The Zen of Python (lines 17-17):", self.zen_list[17]), + "0:-1": ("The Zen of Python (lines 0-17):", "\n".join(self.zen_list[0:-1])), + "10:13": ("The Zen of Python (lines 10-12):", "\n".join(self.zen_list[10:13])) + } + + for input_slice, (title, description) in expected_results.items(): + self.template_embed.title = title + self.template_embed.description = description + + ctx = MockContext() + with self.subTest(input_slice=input_slice, expected_title=title, expected_description=description): + await self.cog.zen.callback(self.cog, ctx, search_value=input_slice) + ctx.send.assert_called_once_with(embed = self.template_embed) + + async def test_zen_with_invalid_slices(self): + """ Tests if the `!zen` command reacts properly to invalid slices for indexing as an argument. """ + slices= ["19:", "10:9", "-1:-2", "0:20", "-100:", "0:-100"] + + for input_slice in slices: + with self.subTest(input_slice = input_slice), self.assertRaises(BadArgument): + await self.cog.zen.callback(self.cog, self.ctx, search_value=input_slice) |