-
-
Notifications
You must be signed in to change notification settings - Fork 247
feat: add .yoda command #1656
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: add .yoda command #1656
Changes from all commits
c743526
edcd343
e0c9c30
128f494
f417760
b08bd07
396a5ec
648dd69
80ff046
f47e6e6
cdaf843
97f92da
f38612d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import re | ||
from discord.ext import commands | ||
from discord import AllowedMentions | ||
|
||
from bot.bot import Bot | ||
|
||
|
||
class Yodaify(commands.Cog): | ||
"""Cog for the yodaify command.""" | ||
|
||
def _yodaify_sentence(self, sentence: str) -> str: | ||
"""Convert a single sentence to Yoda speech pattern.""" | ||
sentence = sentence.strip().rstrip('.') | ||
|
||
# Basic pattern matching for subject-verb-object | ||
# Looking for patterns like "I am driving a car" -> "Driving a car, I am" | ||
words = sentence.split() | ||
if len(words) < 3: | ||
return sentence + "." | ||
|
||
# Common subject-verb patterns to identify the split point | ||
subject_verb_patterns = [ | ||
(r'^(i|you|he|she|it|we|they)\s+(am|are|is|was|were)\s+', 2), | ||
(r'^(i|you|he|she|it|we|they)\s+\w+\s+', 2), | ||
] | ||
|
||
for pattern, split_index in subject_verb_patterns: | ||
if re.match(pattern, sentence.lower()): | ||
subject = ' '.join(words[:split_index]) | ||
predicate = ' '.join(words[split_index:]) | ||
if predicate: | ||
return f"{predicate.lower()}, {subject.lower()}." | ||
|
||
# If no pattern matches, return original with message | ||
return None | ||
|
||
@commands.command(name="yoda") | ||
async def yodaify(self, ctx: commands.Context, *, text: str | None) -> None: | ||
""" | ||
Convert the provided text into Yoda-like speech. | ||
|
||
The command transforms sentences from subject-verb-object format | ||
to object-subject-verb format, similar to how Yoda speaks. | ||
""" | ||
if not text: | ||
return # Help message handled by Discord.py's help system | ||
Comment on lines
+45
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you just type |
||
|
||
# Split into sentences (considering multiple punctuation types) | ||
sentences = re.split(r'[.!?]+\s*', text.strip()) | ||
sentences = [s for s in sentences if s] | ||
|
||
yoda_sentences = [] | ||
any_converted = False | ||
|
||
for sentence in sentences: | ||
yoda_sentence = self._yodaify_sentence(sentence) | ||
if yoda_sentence is None: | ||
yoda_sentences.append(sentence + ".") | ||
else: | ||
any_converted = True | ||
yoda_sentences.append(yoda_sentence) | ||
|
||
if not any_converted: | ||
await ctx.send( | ||
f"Yodafication this doesn't need, {ctx.author.display_name}!\n>>> {text}", | ||
allowed_mentions=AllowedMentions.none() | ||
) | ||
return | ||
|
||
for i in range(len(yoda_sentences)): | ||
sentence = yoda_sentences[i] | ||
words = sentence.split() | ||
for j in range(len(words)): | ||
if words[j].lower() == "i": | ||
words[j] = "I" | ||
sentence = ' '.join(words) | ||
sentence = sentence[0].upper() + sentence[1:] | ||
yoda_sentences[i] = sentence | ||
result = ' '.join(yoda_sentences) | ||
|
||
await ctx.send( | ||
f">>> {result}", | ||
allowed_mentions=AllowedMentions.none() | ||
) | ||
|
||
|
||
async def setup(bot: Bot) -> None: | ||
"""Loads the yodaify cog.""" | ||
await bot.add_cog(Yodaify()) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import pytest | ||
from unittest.mock import AsyncMock, MagicMock, patch | ||
from bot.exts.fun.yodaify import Yodaify | ||
|
||
|
||
@pytest.mark.asyncio | ||
@patch("bot.exts.fun.yodaify.Yodaify.yodaify", new_callable=AsyncMock) | ||
async def test_yodaify_command_is_called(mock_yodaify): | ||
""" | ||
Requirement 1 Bot-command: When a user writes .yoda <text> it runs the function. | ||
""" | ||
ctx = AsyncMock() | ||
|
||
# Simulate a user writing ".yodaify I am driving a car." | ||
await mock_yodaify(ctx, text="I am driving a car.") | ||
|
||
# Verify that the command was indeed called with the expected arguments | ||
mock_yodaify.assert_awaited_once_with(ctx, text="I am driving a car.") | ||
|
||
|
||
async def yodaify_conversion_helper(text, converted_text): | ||
""" | ||
Requirement 5 Format: The returned text should have the format object-subject-verb. | ||
Requirement 7 Consistency: No words should be lost during the conversion. | ||
Requirement 8 Capitalization: The sentence should be capitalized correctly. | ||
""" | ||
|
||
cog = Yodaify() | ||
|
||
mock_ctx = MagicMock() | ||
mock_ctx.author.display_name = "TestUser" | ||
mock_ctx.send = AsyncMock() | ||
mock_ctx.author.edit = AsyncMock() | ||
|
||
await cog.yodaify.callback(cog, mock_ctx, text=text) | ||
|
||
# Ensure a message was sent | ||
mock_ctx.send.assert_called_once() | ||
args, kwargs = mock_ctx.send.call_args | ||
sent_message = args[0] | ||
assert sent_message == converted_text, f"Unexpected sent message: {sent_message}" | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_yodaify_conversion_1(): | ||
await yodaify_conversion_helper("I like trains.", ">>> " + "Trains, I like.") | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_yodaify_conversion_2(): | ||
await yodaify_conversion_helper("I am driving a car.", ">>> " + "Driving a car, I am.") | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_yodaify_conversion_3(): | ||
await yodaify_conversion_helper("She likes my new van.", ">>> " + "My new van, she likes.") | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_yodaify_conversion_4(): | ||
await yodaify_conversion_helper("We should get out of here.", ">>> " + "Get out of here, we should.") | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_yodaify_invalid_sentecne(): | ||
""" | ||
Requirement 6 Invalid sentence: If no changes to the format can be made, it should return: “Yodafication this doesn't need {username}!” + the original text. | ||
""" | ||
await yodaify_conversion_helper("sghafuj fhaslkhglf ajshflka.", "Yodafication this doesn't need, TestUser!" + "\n>>> " + "sghafuj fhaslkhglf ajshflka.") | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_yodaify_multiple_sentances(): | ||
""" | ||
Requirement 9 Multiple sentences: If there are multiple sentences in the input, they should be converted separately. | ||
""" | ||
await yodaify_conversion_helper("I like trains. I am driving a car. She likes my new van.", ">>> " + "Trains, I like. Driving a car, I am. My new van, she likes.") | ||
|
||
Comment on lines
+1
to
+78
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I appreciate the effort put in to write tests, though I'm not sure about these since we don't currently have tests on this project. If we did want to keep them we would want to ensure they are run in CI, documented in the contributing guide, and dependencies are added, However, we have previously decided against this to keep this project as friendly as possible to new contributors. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason this doesn't
return None
, as because it doesn't sentences with less than 3 words don't get the "Yodafication this doesn't need, wookie!" message even though they weren't changed.I think you could also just remove the check, it doesn't seem like it would break anything lower down.