Repository: python-discord/bot

Issue:
create command for getting parts of the zen of python
it would be nice to be able to get specific parts of the zen of python from a command. for example,

`!zen 13` would retrieve the fourteenth line (because we index from zero, obviously).

`!zen namespaces` would search for the string "namespaces", and produce the line which matches best. 

`!zen` without any arguments would still produce the entire thing in the same way that the `zen` tag does.

i think this is reasonably simple, and could come in handy from time to time. :D

edit: also, i'd quite like to implement this. seems simple enough while i get used to all of the new changes since last year :D


Discussion:

Resolving patch:
diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py
--- a/bot/cogs/utils.py
+++ b/bot/cogs/utils.py
@@ -1,14 +1,15 @@
+import difflib
 import logging
 import re
 import unicodedata
 from asyncio import TimeoutError, sleep
 from email.parser import HeaderParser
 from io import StringIO
-from typing import Tuple
+from typing import Tuple, Union
 
 from dateutil import relativedelta
 from discord import Colour, Embed, Message, Role
-from discord.ext.commands import Cog, Context, command
+from discord.ext.commands import BadArgument, Cog, Context, command
 
 from bot.bot import Bot
 from bot.constants import Channels, MODERATION_ROLES, Mention, STAFF_ROLES
@@ -17,6 +18,28 @@
 
 log = logging.getLogger(__name__)
 
+ZEN_OF_PYTHON = """\
+Beautiful is better than ugly.
+Explicit is better than implicit.
+Simple is better than complex.
+Complex is better than complicated.
+Flat is better than nested.
+Sparse is better than dense.
+Readability counts.
+Special cases aren't special enough to break the rules.
+Although practicality beats purity.
+Errors should never pass silently.
+Unless explicitly silenced.
+In the face of ambiguity, refuse the temptation to guess.
+There should be one-- and preferably only one --obvious way to do it.
+Although that way may not be obvious at first unless you're Dutch.
+Now is better than never.
+Although never is often better than *right* now.
+If the implementation is hard to explain, it's a bad idea.
+If the implementation is easy to explain, it may be a good idea.
+Namespaces are one honking great idea -- let's do more of those!
+"""
+
 
 class Utils(Cog):
     """A selection of utilities which don't have a clear category."""
@@ -173,6 +196,67 @@ def check(m: Message) -> bool:
             f"as I detected unauthorised use by {msg.author} (ID: {msg.author.id})."
         )
 
+    @command()
+    async def zen(self, ctx: Context, *, search_value: Union[int, str, None] = None) -> None:
+        """
+        Show the Zen of Python.
+
+        Without any arguments, the full Zen will be produced.
+        If an integer is provided, the line with that index will be produced.
+        If a string is provided, the line which matches best will be produced.
+        """
+        embed = Embed(
+            colour=Colour.blurple(),
+            title="The Zen of Python",
+            description=ZEN_OF_PYTHON
+        )
+
+        if search_value is None:
+            embed.title += ", by Tim Peters"
+            await ctx.send(embed=embed)
+            return
+
+        zen_lines = ZEN_OF_PYTHON.splitlines()
+
+        # handle if it's an index int
+        if isinstance(search_value, int):
+            upper_bound = len(zen_lines) - 1
+            lower_bound = -1 * upper_bound
+            if not (lower_bound <= search_value <= upper_bound):
+                raise BadArgument(f"Please provide an index between {lower_bound} and {upper_bound}.")
+
+            embed.title += f" (line {search_value % len(zen_lines)}):"
+            embed.description = zen_lines[search_value]
+            await ctx.send(embed=embed)
+            return
+
+        # handle if it's a search string
+        matcher = difflib.SequenceMatcher(None, search_value.lower())
+
+        best_match = ""
+        match_index = 0
+        best_ratio = 0
+
+        for index, line in enumerate(zen_lines):
+            matcher.set_seq2(line.lower())
+
+            # the match ratio needs to be adjusted because, naturally,
+            # longer lines will have worse ratios than shorter lines when
+            # fuzzy searching for keywords. this seems to work okay.
+            adjusted_ratio = (len(line) - 5) ** 0.5 * matcher.ratio()
+
+            if adjusted_ratio > best_ratio:
+                best_ratio = adjusted_ratio
+                best_match = line
+                match_index = index
+
+        if not best_match:
+            raise BadArgument("I didn't get a match! Please try again with a different search term.")
+
+        embed.title += f" (line {match_index}):"
+        embed.description = best_match
+        await ctx.send(embed=embed)
+
 
 def setup(bot: Bot) -> None:
     """Load the Utils cog."""


Test patch: