From 560783c3d222d156a073fad5d467cd6331b74ecb Mon Sep 17 00:00:00 2001 From: Rapptz Date: Tue, 12 Mar 2019 05:27:34 -0400 Subject: [PATCH] [commands] Separate view parsing errors from BadArgument. This causes them to be raised from a new exception named ArgumentParsingError with 3 children for ease with i18n. This is technically a breaking change since it no longer derives from BadArgument, though catching UserInputError will prevent this change from affecting the user. --- discord/ext/commands/errors.py | 56 +++++++++++++++++++++++++++++++++- discord/ext/commands/view.py | 10 +++--- docs/ext/commands/api.rst | 16 ++++++++++ 3 files changed, 76 insertions(+), 6 deletions(-) diff --git a/discord/ext/commands/errors.py b/discord/ext/commands/errors.py index 49d6f8d7c..599827570 100644 --- a/discord/ext/commands/errors.py +++ b/discord/ext/commands/errors.py @@ -32,7 +32,9 @@ __all__ = ['CommandError', 'MissingRequiredArgument', 'BadArgument', 'DisabledCommand', 'CommandInvokeError', 'TooManyArguments', 'UserInputError', 'CommandOnCooldown', 'NotOwner', 'MissingPermissions', 'BotMissingPermissions', 'ConversionError', - 'BadUnionArgument'] + 'BadUnionArgument', 'ArgumentParsingError', + 'UnexpectedQuoteError', 'InvalidEndOfQuotedStringError', + 'ExpectedClosingQuoteError', ] class CommandError(DiscordException): r"""The base exception type for all command related errors. @@ -229,3 +231,55 @@ class BadUnionArgument(UserInputError): fmt = ' or '.join(to_string) super().__init__('Could not convert "{0.name}" into {1}.'.format(param, fmt)) + +class ArgumentParsingError(UserInputError): + """An exception raised when the parser fails to parse a user's input. + + This derives from :exc:`UserInputError`. There are child classes + that implement more granular parsing errors for i18n purposes. + """ + pass + +class UnexpectedQuoteError(ArgumentParsingError): + """An exception raised when the parser encounters a quote mark inside a non-quoted string. + + This derives from :exc:`ArgumentParsingError`. + + Attributes + ------------ + quote: :class:`str` + The quote mark that was found inside the non-quoted string. + """ + def __init__(self, quote): + self.quote = quote + super().__init__('Unexpected quote mark, {0!r}, in non-quoted string'.format(quote)) + +class InvalidEndOfQuotedStringError(ArgumentParsingError): + """An exception raised when a space is expected after the closing quote in a string + but a different character is found. + + This derives from :exc:`ArgumentParsingError`. + + Attributes + ----------- + char: :class:`str` + The character found instead of the expected string. + """ + def __init__(self, char): + self.char = char + super().__init__('Expected space after closing quotation but received {0!r}'.format(char)) + +class ExpectedClosingQuoteError(ArgumentParsingError): + """An exception raised when a quote character is expected but not found. + + This derives from :exc:`ArgumentParsingError`. + + Attributes + ----------- + close_quote: :class:`str` + The quote character expected. + """ + + def __init__(self, close_quote): + self.close_quote = close_quote + super().__init__('Expected closing {}.'.format(close_quote)) diff --git a/discord/ext/commands/view.py b/discord/ext/commands/view.py index 251c535a7..ad34e0a43 100644 --- a/discord/ext/commands/view.py +++ b/discord/ext/commands/view.py @@ -24,7 +24,7 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -from .errors import BadArgument +from .errors import UnexpectedQuoteError, InvalidEndOfQuotedStringError, ExpectedClosingQuoteError class StringView: def __init__(self, buffer): @@ -151,7 +151,7 @@ def quoted_word(view): if not current: if is_quoted: # unexpected EOF - raise BadArgument('Expected closing {}.'.format(close_quote)) + raise ExpectedClosingQuoteError(close_quote) return ''.join(result) # currently we accept strings in the format of "hello world" @@ -162,7 +162,7 @@ def quoted_word(view): # string ends with \ and no character after it if is_quoted: # if we're quoted then we're expecting a closing quote - raise BadArgument('Expected closing {}.'.format(close_quote)) + raise ExpectedClosingQuoteError(close_quote) # if we aren't then we just let it through return ''.join(result) @@ -177,14 +177,14 @@ def quoted_word(view): if not is_quoted and current in _all_quotes: # we aren't quoted - raise BadArgument('Unexpected quote mark in non-quoted string') + 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 BadArgument('Expected space after closing quotation') + raise InvalidEndOfQuotedStringError(next_char) # we're quoted so it's okay return ''.join(result) diff --git a/docs/ext/commands/api.rst b/docs/ext/commands/api.rst index 7dc46e08b..47af272d0 100644 --- a/docs/ext/commands/api.rst +++ b/docs/ext/commands/api.rst @@ -226,6 +226,18 @@ Exceptions .. autoexception:: discord.ext.commands.MissingRequiredArgument :members: +.. autoexception:: discord.ext.commands.ArgumentParsingError + :members: + +.. autoexception:: discord.ext.commands.UnexpectedQuoteError + :members: + +.. autoexception:: discord.ext.commands.InvalidEndOfQuotedStringError + :members: + +.. autoexception:: discord.ext.commands.ExpectedClosingQuoteError + :members: + .. autoexception:: discord.ext.commands.BadArgument :members: @@ -278,6 +290,10 @@ Exception Hierarchy - :exc:`~.commands.TooManyArguments` - :exc:`~.commands.BadArgument` - :exc:`~.commands.BadUnionArgument` + - :exc:`~.commands.ArgumentParsingError` + - :exc:`~.commands.UnexpectedQuoteError` + - :exc:`~.commands.InvalidEndOfQuotedStringError` + - :exc:`~.commands.ExpectedClosingQuoteError` - :exc:`~.commands.CommandNotFound` - :exc:`~.commands.CheckFailure` - :exc:`~.commands.NoPrivateMessage`