Add public positive_int and finite_float converters (#5969)

Co-authored-by: Kreusada <67752638+Kreusada@users.noreply.github.com>
This commit is contained in:
AAA3A 2023-04-17 23:33:44 +02:00 committed by GitHub
parent fa305cb060
commit eafbb06756
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 143 additions and 76 deletions

View File

@ -7,13 +7,13 @@ import discord
from redbot.core import commands, Config
from redbot.core.bot import Red
from redbot.core.commands import RawUserIdConverter
from redbot.core.commands import positive_int, RawUserIdConverter
from redbot.core.i18n import Translator, cog_i18n
from redbot.core.utils.chat_formatting import humanize_number
from redbot.core.utils.mod import slow_deletion, mass_purge
from redbot.core.utils.predicates import MessagePredicate
from .checks import check_self_permissions
from .converters import PositiveInt, RawMessageIds, positive_int
from .converters import RawMessageIds
_ = Translator("Cleanup", __file__)
@ -78,9 +78,9 @@ class Cleanup(commands.Cog):
channel: Union[
discord.TextChannel, discord.VoiceChannel, discord.DMChannel, discord.Thread
],
number: Optional[PositiveInt] = None,
number: Optional[int] = None,
check: Callable[[discord.Message], bool] = lambda x: True,
limit: Optional[PositiveInt] = None,
limit: Optional[int] = None,
before: Union[discord.Message, datetime] = None,
after: Union[discord.Message, datetime] = None,
delete_pinned: bool = False,
@ -684,9 +684,7 @@ class Cleanup(commands.Cog):
@commands.guild_only()
@commands.mod_or_permissions(manage_messages=True)
@commands.bot_has_permissions(manage_messages=True)
async def cleanup_duplicates(
self, ctx: commands.Context, number: positive_int = PositiveInt(50)
):
async def cleanup_duplicates(self, ctx: commands.Context, number: positive_int = 50):
"""Deletes duplicate messages in the channel from the last X messages and keeps only one copy.
Defaults to 50.

View File

@ -1,8 +1,5 @@
from typing import NewType, TYPE_CHECKING
from redbot.core.commands import BadArgument, Context, Converter
from redbot.core.i18n import Translator
from redbot.core.utils.chat_formatting import inline
_ = Translator("Cleanup", __file__)
@ -15,18 +12,3 @@ class RawMessageIds(Converter):
return int(argument)
raise BadArgument(_("{} doesn't look like a valid message ID.").format(argument))
PositiveInt = NewType("PositiveInt", int)
if TYPE_CHECKING:
positive_int = PositiveInt
else:
def positive_int(arg: str) -> int:
try:
ret = int(arg)
except ValueError:
raise BadArgument(_("{arg} is not an integer.").format(arg=inline(arg)))
if ret <= 0:
raise BadArgument(_("{arg} is not a positive integer.").format(arg=inline(arg)))
return ret

View File

@ -1,22 +0,0 @@
from typing import NewType, TYPE_CHECKING
from redbot.core.commands import BadArgument
from redbot.core.i18n import Translator
from redbot.core.utils.chat_formatting import inline
_ = Translator("Economy", __file__)
# Duplicate of redbot.cogs.cleanup.converters.PositiveInt
PositiveInt = NewType("PositiveInt", int)
if TYPE_CHECKING:
positive_int = PositiveInt
else:
def positive_int(arg: str) -> int:
try:
ret = int(arg)
except ValueError:
raise BadArgument(_("{arg} is not an integer.").format(arg=inline(arg)))
if ret <= 0:
raise BadArgument(_("{arg} is not a positive integer.").format(arg=inline(arg)))
return ret

View File

@ -5,18 +5,17 @@ from collections import defaultdict, deque, namedtuple
from datetime import datetime, timezone, timedelta
from enum import Enum
from math import ceil
from typing import cast, Iterable, Union, Literal
from typing import cast, Iterable, Literal
import discord
from redbot.core import Config, bank, commands, errors
from redbot.core.commands.converter import TimedeltaConverter
from redbot.core.commands.converter import TimedeltaConverter, positive_int
from redbot.core.bot import Red
from redbot.core.i18n import Translator, cog_i18n
from redbot.core.utils import AsyncIter
from redbot.core.utils.chat_formatting import box, humanize_number
from redbot.core.utils.menus import close_menu, menu
from .converters import positive_int
from redbot.core.utils.menus import menu
T_ = Translator("Economy", __file__)

View File

@ -4,10 +4,10 @@ import pkgutil
from importlib import import_module, invalidate_caches
from importlib.machinery import ModuleSpec
from pathlib import Path
from typing import TYPE_CHECKING, Union, List, Optional
from typing import Union, List, Optional
import redbot.cogs
from redbot.core.commands import BadArgument
from redbot.core.commands import positive_int
from redbot.core.utils import deduplicate_iterables
import discord
@ -21,21 +21,6 @@ from .utils.chat_formatting import box, pagify, humanize_list, inline
__all__ = ["CogManager"]
# Duplicate of redbot.cogs.cleanup.converters.positive_int
if TYPE_CHECKING:
positive_int = int
else:
def positive_int(arg: str) -> int:
try:
ret = int(arg)
except ValueError:
raise BadArgument(_("{arg} is not an integer.").format(arg=inline(arg)))
if ret <= 0:
raise BadArgument(_("{arg} is not a positive integer.").format(arg=inline(arg)))
return ret
class NoSuchCog(ImportError):
"""Thrown when a cog is missing.

View File

@ -28,10 +28,12 @@ from .converter import (
DictConverter as DictConverter,
RelativedeltaConverter as RelativedeltaConverter,
TimedeltaConverter as TimedeltaConverter,
finite_float as finite_float,
get_dict_converter as get_dict_converter,
get_timedelta_converter as get_timedelta_converter,
parse_relativedelta as parse_relativedelta,
parse_timedelta as parse_timedelta,
positive_int as positive_int,
NoParseOptional as NoParseOptional,
UserInputOptional as UserInputOptional,
RawUserIdConverter as RawUserIdConverter,

View File

@ -6,6 +6,7 @@ This module contains useful functions and classes for command argument conversio
Some of the converters within are included provisionally and are marked as such.
"""
import functools
import math
import re
from datetime import timedelta
from dateutil.relativedelta import relativedelta
@ -37,10 +38,12 @@ __all__ = [
"NoParseOptional",
"RelativedeltaConverter",
"TimedeltaConverter",
"finite_float",
"get_dict_converter",
"get_timedelta_converter",
"parse_relativedelta",
"parse_timedelta",
"positive_int",
"CommandConverter",
"CogConverter",
]
@ -233,6 +236,26 @@ class RawUserIdConverter(dpy_commands.Converter):
# which is *not* for type checking for the actual implementation
# and ensure the lies stay correct for how the object should look as a typehint
positive_int = dpy_commands.Range[int, 0, None]
if TYPE_CHECKING:
finite_float = float
else:
def finite_float(arg: str) -> float:
"""
This converts a user provided string into a finite float.
"""
try:
ret = float(arg)
except ValueError:
raise BadArgument(_("`{arg}` is not a number.").format(arg=arg))
if not math.isfinite(ret):
raise BadArgument(_("`{arg}` is not a finite number.").format(arg=ret))
return ret
if TYPE_CHECKING:
DictConverter = Dict[str, str]
else:

View File

@ -2,7 +2,6 @@ import asyncio
import contextlib
import platform
import sys
import codecs
import logging
import traceback
from datetime import datetime, timedelta, timezone
@ -17,12 +16,9 @@ from redbot.core import data_manager
from redbot.core.commands import RedHelpFormatter, HelpSettings
from redbot.core.i18n import (
Translator,
set_contextual_locale,
set_contextual_regional_format,
set_contextual_locales_from_guild,
)
from .utils import AsyncIter
from .. import __version__ as red_version, version_info as red_version_info, VersionInfo
from .. import __version__ as red_version, version_info as red_version_info
from . import commands
from .config import get_latest_confs
from .utils._internal_utils import (
@ -32,7 +28,7 @@ from .utils._internal_utils import (
fetch_latest_red_version_info,
send_to_owners_with_prefix_replaced,
)
from .utils.chat_formatting import inline, format_perms_list, humanize_timedelta
from .utils.chat_formatting import inline, format_perms_list
import rich
from rich import box
@ -229,6 +225,8 @@ def init_events(bot, cli_flags):
return
if not isinstance(error, commands.CommandNotFound):
asyncio.create_task(bot._delete_delay(ctx))
converter = getattr(ctx.current_parameter, "converter", None)
argument = ctx.current_argument
if isinstance(error, commands.MissingRequiredArgument):
await ctx.send_help()
@ -241,10 +239,112 @@ def init_events(bot, cli_flags):
await ctx.send(msg)
if error.send_cmd_help:
await ctx.send_help()
elif isinstance(error, commands.RangeError):
if isinstance(error.value, int):
if error.minimum == 0 and error.maximum is None:
message = _("Argument `{parameter_name}` must be a positive integer.")
elif error.minimum is None and error.maximum is not None:
message = _(
"Argument `{parameter_name}` must be an integer no more than {maximum}."
)
elif error.minimum is not None and error.maximum is None:
message = _(
"Argument `{parameter_name}` must be an integer no less than {minimum}."
)
elif error.maximum is not None and error.minimum is not None:
message = _(
"Argument `{parameter_name}` must be an integer between {minimum} and {maximum}."
)
elif isinstance(error.value, float):
if error.minimum == 0 and error.maximum is None:
message = _("Argument `{parameter_name}` must be a positive number.")
elif error.minimum is None and error.maximum is not None:
message = _(
"Argument `{parameter_name}` must be a number no more than {maximum}."
)
elif error.minimum is not None and error.maximum is None:
message = _(
"Argument `{parameter_name}` must be a number no less than {maximum}."
)
elif error.maximum is not None and error.minimum is not None:
message = _(
"Argument `{parameter_name}` must be a number between {minimum} and {maximum}."
)
elif isinstance(error.value, str):
if error.minimum is None and error.maximum is not None:
message = _(
"Argument `{parameter_name}` must be a string with a length of no more than {maximum}."
)
elif error.minimum is not None and error.maximum is None:
message = _(
"Argument `{parameter_name}` must be a string with a length of no less than {minimum}."
)
elif error.maximum is not None and error.minimum is not None:
message = _(
"Argument `{parameter_name}` must be a string with a length of between {minimum} and {maximum}."
)
await ctx.send(
message.format(
maximum=error.maximum,
minimum=error.minimum,
parameter_name=ctx.current_parameter.name,
)
)
return
elif isinstance(error, commands.BadArgument):
if isinstance(converter, commands.Range):
if converter.annotation is int:
if converter.min == 0 and converter.max is None:
message = _("Argument `{parameter_name}` must be a positive integer.")
elif converter.min is None and converter.max is not None:
message = _(
"Argument `{parameter_name}` must be an integer no more than {maximum}."
)
elif converter.min is not None and converter.max is None:
message = _(
"Argument `{parameter_name}` must be an integer no less than {minimum}."
)
elif converter.max is not None and converter.min is not None:
message = _(
"Argument `{parameter_name}` must be an integer between {minimum} and {maximum}."
)
elif converter.annotation is float:
if converter.min == 0 and converter.max is None:
message = _("Argument `{parameter_name}` must be a positive number.")
elif converter.min is None and converter.max is not None:
message = _(
"Argument `{parameter_name}` must be a number no more than {maximum}."
)
elif converter.min is not None and converter.max is None:
message = _(
"Argument `{parameter_name}` must be a number no less than {minimum}."
)
elif converter.max is not None and converter.min is not None:
message = _(
"Argument `{parameter_name}` must be a number between {minimum} and {maximum}."
)
elif converter.annotation is str:
if error.minimum is None and error.maximum is not None:
message = _(
"Argument `{parameter_name}` must be a string with a length of no more than {maximum}."
)
elif error.minimum is not None and error.maximum is None:
message = _(
"Argument `{parameter_name}` must be a string with a length of no less than {minimum}."
)
elif error.maximum is not None and error.minimum is not None:
message = _(
"Argument `{parameter_name}` must be a string with a length of between {minimum} and {maximum}."
)
await ctx.send(
message.format(
maximum=converter.max,
minimum=converter.min,
parameter_name=ctx.current_parameter.name,
)
)
return
if isinstance(error.__cause__, ValueError):
converter = ctx.current_parameter.converter
argument = ctx.current_argument
if converter is int:
await ctx.send(_('"{argument}" is not an integer.').format(argument=argument))
return