diff --git a/discord/ext/commands/core.py b/discord/ext/commands/core.py index e652467b0..85bb1b9f1 100644 --- a/discord/ext/commands/core.py +++ b/discord/ext/commands/core.py @@ -33,7 +33,6 @@ import discord from .errors import * from .cooldowns import Cooldown, BucketType, CooldownMapping -from .view import quoted_word from . import converter as converters from ._types import _BaseCommand from .cog import Cog @@ -421,7 +420,7 @@ class Command(_BaseCommand): if consume_rest_is_special: argument = view.read_rest().strip() else: - argument = quoted_word(view) + argument = view.get_quoted_word() view.previous = previous return await self.do_conversion(ctx, converter, argument, param) @@ -434,7 +433,7 @@ class Command(_BaseCommand): previous = view.index view.skip_ws() - argument = quoted_word(view) + argument = view.get_quoted_word() try: value = await self.do_conversion(ctx, converter, argument, param) except CommandError: @@ -450,7 +449,7 @@ class Command(_BaseCommand): async def _transform_greedy_var_pos(self, ctx, param, converter): view = ctx.view previous = view.index - argument = quoted_word(view) + argument = view.get_quoted_word() try: value = await self.do_conversion(ctx, converter, argument, param) except CommandError: diff --git a/discord/ext/commands/view.py b/discord/ext/commands/view.py index ad34e0a43..91cc06b2b 100644 --- a/discord/ext/commands/view.py +++ b/discord/ext/commands/view.py @@ -26,6 +26,28 @@ DEALINGS IN THE SOFTWARE. from .errors import UnexpectedQuoteError, InvalidEndOfQuotedStringError, ExpectedClosingQuoteError +# map from opening quotes to closing quotes +_quotes = { + '"': '"', + "‘": "’", + "‚": "‛", + "“": "”", + "„": "‟", + "⹂": "⹂", + "「": "」", + "『": "』", + "〝": "〞", + "﹁": "﹂", + "﹃": "﹄", + """: """, + "「": "」", + "«": "»", + "‹": "›", + "《": "》", + "〈": "〉", +} +_all_quotes = set(_quotes.keys()) | set(_quotes.values()) + class StringView: def __init__(self, buffer): self.index = 0 @@ -104,93 +126,69 @@ class StringView: self.index += pos return result - def __repr__(self): - return ''.format(self) + def get_quoted_word(self): + current = self.current + if current is None: + return None -# Parser + close_quote = _quotes.get(current) + is_quoted = bool(close_quote) + if is_quoted: + result = [] + _escaped_quotes = (current, close_quote) + else: + result = [current] + _escaped_quotes = _all_quotes -# map from opening quotes to closing quotes -_quotes = { - '"': '"', - "‘": "’", - "‚": "‛", - "“": "”", - "„": "‟", - "⹂": "⹂", - "「": "」", - "『": "』", - "〝": "〞", - "﹁": "﹂", - "﹃": "﹄", - """: """, - "「": "」", - "«": "»", - "‹": "›", - "《": "》", - "〈": "〉", -} -_all_quotes = set(_quotes.keys()) | set(_quotes.values()) - -def quoted_word(view): - current = view.current - - if current is None: - return None - - close_quote = _quotes.get(current) - is_quoted = bool(close_quote) - if is_quoted: - result = [] - _escaped_quotes = (current, close_quote) - else: - result = [current] - _escaped_quotes = _all_quotes - - while not view.eof: - current = view.get() - if not current: - if is_quoted: - # unexpected EOF - raise ExpectedClosingQuoteError(close_quote) - return ''.join(result) - - # currently we accept strings in the format of "hello world" - # to embed a quote inside the string you must escape it: "a \"world\"" - if current == '\\': - next_char = view.get() - if not next_char: - # string ends with \ and no character after it + while not self.eof: + current = self.get() + if not current: if is_quoted: - # if we're quoted then we're expecting a closing quote + # unexpected EOF raise ExpectedClosingQuoteError(close_quote) - # if we aren't then we just let it through return ''.join(result) - if next_char in _escaped_quotes: - # escaped quote - result.append(next_char) - else: - # different escape character, ignore it - view.undo() - result.append(current) - continue - - if not is_quoted and current in _all_quotes: - # we aren't quoted - raise UnexpectedQuoteError(current) - - # closing quote - if is_quoted and current == close_quote: - next_char = view.get() - valid_eof = not next_char or next_char.isspace() - if not valid_eof: - raise InvalidEndOfQuotedStringError(next_char) - - # we're quoted so it's okay - return ''.join(result) - - if current.isspace() and not is_quoted: - # end of word found - return ''.join(result) - - result.append(current) + # currently we accept strings in the format of "hello world" + # to embed a quote inside the string you must escape it: "a \"world\"" + if current == '\\': + next_char = self.get() + if not next_char: + # string ends with \ and no character after it + if is_quoted: + # if we're quoted then we're expecting a closing quote + raise ExpectedClosingQuoteError(close_quote) + # if we aren't then we just let it through + return ''.join(result) + + if next_char in _escaped_quotes: + # escaped quote + result.append(next_char) + else: + # different escape character, ignore it + self.undo() + result.append(current) + continue + + if not is_quoted and current in _all_quotes: + # we aren't quoted + raise UnexpectedQuoteError(current) + + # closing quote + if is_quoted and current == close_quote: + next_char = self.get() + valid_eof = not next_char or next_char.isspace() + if not valid_eof: + raise InvalidEndOfQuotedStringError(next_char) + + # we're quoted so it's okay + return ''.join(result) + + if current.isspace() and not is_quoted: + # end of word found + return ''.join(result) + + result.append(current) + + + def __repr__(self): + return ''.format(self)