diff options
| author | 2020-05-06 13:30:51 -0700 | |
|---|---|---|
| committer | 2020-06-13 11:21:05 -0700 | |
| commit | 2a7dcccf7a6b352e3f43b4248d00d9ec15af243e (patch) | |
| tree | 5ff3902606e17150e2c3839fa58b5addd6007e9d | |
| parent | Code block: move parsing functions to a separate module (diff) | |
Code block: rework the instruction formatting functions
A new module, `instructions`, was created to house the functions.
4 ways in which code blocks can be incorrect are considered:
1. The code is not within a code block at all
2. Incorrect characters are used for back ticks
3. A language is not specified
4. A language is specified incorrectly
Splitting it up into these 4 cases allows for more specific and relevant
instructions to be shown to users. If a message has both incorrect back
ticks and an issue with the language specifier, the instructions for
fixing both issues are combined.
The instructions show a generic code example rather than using the
original code from the message. This circumvents any ambiguities when
parsing their message and trying to fix it. The escaped code block also
failed to preserve indentation. This was a problem because some users
would copy it anyway and end up with poorly formatted code. By using
a simple example that doesn't rely on indentation, it makes it clear
the example is not meant to be copied. Finally, the new examples are
shorter and thus make the embed not as giant.
| -rw-r--r-- | bot/cogs/codeblock/cog.py | 63 | ||||
| -rw-r--r-- | bot/cogs/codeblock/instructions.py | 113 | ||||
| -rw-r--r-- | bot/cogs/codeblock/parsing.py | 2 |
3 files changed, 113 insertions, 65 deletions
diff --git a/bot/cogs/codeblock/cog.py b/bot/cogs/codeblock/cog.py index dad0cc9cc..efc22c8a5 100644 --- a/bot/cogs/codeblock/cog.py +++ b/bot/cogs/codeblock/cog.py @@ -1,6 +1,5 @@ import logging import time -from typing import Optional import discord from discord import Embed, Message, RawMessageUpdateEvent @@ -33,68 +32,6 @@ class CodeBlockCog(Cog, name="Code Block"): # Stores improperly formatted Python codeblock message ids and the corresponding bot message self.codeblock_message_ids = {} - def format_bad_ticks_message(self, message: discord.Message) -> Optional[str]: - """Return the guide message to output for bad code block ticks in `message`.""" - ticks = message.content[:3] - content = self.codeblock_stripping(f"```{message.content[3:-3]}```", True) - if content is None: - return - - content, repl_code = content - - if len(content) == 2: - content = content[1] - else: - content = content[0] - - content = parsing.truncate(content) - content_escaped_markdown = parsing.RE_MARKDOWN.sub(r'\\\1', content) - - return ( - "It looks like you are trying to paste code into this channel.\n\n" - "You seem to be using the wrong symbols to indicate where the codeblock should start. " - f"The correct symbols would be \\`\\`\\`, not `{ticks}`.\n\n" - "**Here is an example of how it should look:**\n" - f"\\`\\`\\`python\n{content_escaped_markdown}\n\\`\\`\\`\n\n" - "**This will result in the following:**\n" - f"```python\n{content}\n```" - ) - - def format_guide_message(self, message: discord.Message) -> Optional[str]: - """Return the guide message to output for a poorly formatted code block in `message`.""" - content = self.codeblock_stripping(message.content, False) - if content is None: - return - - content, repl_code = content - - if not repl_code and not parsing.is_python_code(content[0]): - return - - if content and repl_code: - content = content[1] - else: - content = content[0] - - content = parsing.truncate(content) - - log.debug( - f"{message.author} posted something that needed to be put inside python code " - f"blocks. Sending the user some instructions." - ) - - content_escaped_markdown = parsing.RE_MARKDOWN.sub(r'\\\1', content) - return ( - "It looks like you're trying to paste code into this channel.\n\n" - "Discord has support for Markdown, which allows you to post code with full " - "syntax highlighting. Please use these whenever you paste code, as this " - "helps improve the legibility and makes it easier for us to help you.\n\n" - f"**To do this, use the following method:**\n" - f"\\`\\`\\`python\n{content_escaped_markdown}\n\\`\\`\\`\n\n" - "**This will result in the following:**\n" - f"```python\n{content}\n```" - ) - @staticmethod def is_help_channel(channel: discord.TextChannel) -> bool: """Return True if `channel` is in one of the help categories.""" diff --git a/bot/cogs/codeblock/instructions.py b/bot/cogs/codeblock/instructions.py new file mode 100644 index 000000000..0bcd2eda8 --- /dev/null +++ b/bot/cogs/codeblock/instructions.py @@ -0,0 +1,113 @@ +import logging +from typing import Optional + +from . import parsing + +log = logging.getLogger(__name__) + +PY_LANG_CODES = ("python", "py") +EXAMPLE_PY = f"python\nprint('Hello, world!')" # Make sure to escape any Markdown symbols here. +EXAMPLE_CODE_BLOCKS = ( + "\\`\\`\\`{content}\n\\`\\`\\`\n\n" + "**This will result in the following:**\n" + "```{content}```" +) + + +def get_bad_ticks_message(code_block: parsing.CodeBlock) -> Optional[str]: + """Return instructions on using the correct ticks for `code_block`.""" + valid_ticks = f"\\{parsing.BACKTICK}" * 3 + + # The space at the end is important here because something may be appended! + instructions = ( + "It looks like you are trying to paste code into this channel.\n\n" + "You seem to be using the wrong symbols to indicate where the code block should start. " + f"The correct symbols would be {valid_ticks}, not `{code_block.tick * 3}`. " + ) + + # Check if the code has an issue with the language specifier. + addition_msg = get_bad_lang_message(code_block.content) + if not addition_msg: + addition_msg = get_no_lang_message(code_block.content) + + # Combine the back ticks message with the language specifier message. The latter will + # already have an example code block. + if addition_msg: + # The first line has a double line break which is not desirable when appending the msg. + addition_msg = addition_msg.replace("\n\n", "\n", 1) + + # Make the first character of the addition lower case. + instructions += "Furthermore, " + addition_msg[0].lower() + addition_msg[1:] + else: + # Determine the example code to put in the code block based on the language specifier. + if code_block.language.lower() in PY_LANG_CODES: + content = EXAMPLE_PY + elif code_block.language: + # It's not feasible to determine what would be a valid example for other languages. + content = f"{code_block.language}\n..." + else: + content = "Hello, world!" + + example_blocks = EXAMPLE_CODE_BLOCKS.format(content) + instructions += f"\n\n**Here is an example of how it should look:**\n{example_blocks}" + + return instructions + + +def get_no_ticks_message(content: str) -> Optional[str]: + """If `content` is Python/REPL code, return instructions on using code blocks.""" + if parsing.is_repl_code(content) or parsing.is_python_code(content): + example_blocks = EXAMPLE_CODE_BLOCKS.format(EXAMPLE_PY) + return ( + "It looks like you're trying to paste code into this channel.\n\n" + "Discord has support for Markdown, which allows you to post code with full " + "syntax highlighting. Please use these whenever you paste code, as this " + "helps improve the legibility and makes it easier for us to help you.\n\n" + f"**To do this, use the following method:**\n{example_blocks}" + ) + + +def get_bad_lang_message(content: str) -> Optional[str]: + """ + Return instructions on fixing the Python language specifier for a code block. + + If `content` doesn't start with "python" or "py" as the language specifier, return None. + """ + stripped = content.lstrip().lower() + lang = next((lang for lang in PY_LANG_CODES if stripped.startswith(lang)), None) + + if lang: + # Note that get_bad_ticks_message expects the first line to have an extra newline. + lines = ["It looks like you incorrectly specified a language for your code block.\n"] + + if content.startswith(" "): + lines.append(f"Make sure there are no spaces between the back ticks and `{lang}`.") + + if stripped[len(lang)] != "\n": + lines.append( + f"Make sure you put your code on a new line following `{lang}`. " + f"There must not be any spaces after `{lang}`." + ) + + example_blocks = EXAMPLE_CODE_BLOCKS.format(EXAMPLE_PY) + lines.append(f"\n**Here is an example of how it should look:**\n{example_blocks}") + + return "\n".join(lines) + + +def get_no_lang_message(content: str) -> Optional[str]: + """ + Return instructions on specifying a language for a code block. + + If `content` is not valid Python or Python REPL code, return None. + """ + if parsing.is_repl_code(content) or parsing.is_python_code(content): + example_blocks = EXAMPLE_CODE_BLOCKS.format(EXAMPLE_PY) + + # Note that get_bad_ticks_message expects the first line to have an extra newline. + return ( + "It looks like you pasted Python code without syntax highlighting.\n\n" + "Please use syntax highlighting to improve the legibility of your code and make" + "it easier for us to help you.\n\n" + f"**To do this, use the following method:**\n{example_blocks}" + ) diff --git a/bot/cogs/codeblock/parsing.py b/bot/cogs/codeblock/parsing.py index 7a096758b..d541441e0 100644 --- a/bot/cogs/codeblock/parsing.py +++ b/bot/cogs/codeblock/parsing.py @@ -7,8 +7,6 @@ import discord log = logging.getLogger(__name__) -RE_MARKDOWN = re.compile(r'([*_~`|>])') -RE_CODE_BLOCK_LANGUAGE = re.compile(r"```(?:[^\W_]+)\n(.*?)```", re.DOTALL) BACKTICK = "`" TICKS = { BACKTICK, |