mirror of
https://github.com/Rapptz/discord.py.git
synced 2025-07-21 02:16:41 +00:00
[commands] Add support for NumPy style docstrings for commands
This commit is contained in:
parent
27b19ed582
commit
9ed5fbecea
@ -44,6 +44,7 @@ from typing import (
|
|||||||
Union,
|
Union,
|
||||||
overload,
|
overload,
|
||||||
)
|
)
|
||||||
|
import re
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
|
|
||||||
@ -54,6 +55,7 @@ from .converter import Greedy, run_converters
|
|||||||
from .cooldowns import BucketType, Cooldown, CooldownMapping, DynamicCooldownMapping, MaxConcurrency
|
from .cooldowns import BucketType, Cooldown, CooldownMapping, DynamicCooldownMapping, MaxConcurrency
|
||||||
from .errors import *
|
from .errors import *
|
||||||
from .parameters import Parameter, Signature
|
from .parameters import Parameter, Signature
|
||||||
|
from discord.app_commands.commands import NUMPY_DOCSTRING_ARG_REGEX
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing_extensions import Concatenate, ParamSpec, Self
|
from typing_extensions import Concatenate, ParamSpec, Self
|
||||||
@ -165,6 +167,43 @@ def get_signature_parameters(
|
|||||||
return params
|
return params
|
||||||
|
|
||||||
|
|
||||||
|
PARAMETER_HEADING_REGEX = re.compile(r'Parameters?\n---+\n', re.I)
|
||||||
|
|
||||||
|
|
||||||
|
def _fold_text(input: str) -> str:
|
||||||
|
"""Turns a single newline into a space, and multiple newlines into a newline."""
|
||||||
|
|
||||||
|
def replacer(m: re.Match[str]) -> str:
|
||||||
|
if len(m.group()) <= 1:
|
||||||
|
return ' '
|
||||||
|
return '\n'
|
||||||
|
|
||||||
|
return re.sub(r'\n+', replacer, inspect.cleandoc(input))
|
||||||
|
|
||||||
|
|
||||||
|
def extract_descriptions_from_docstring(function: Callable[..., Any], params: Dict[str, Parameter], /) -> Optional[str]:
|
||||||
|
docstring = inspect.getdoc(function)
|
||||||
|
|
||||||
|
if docstring is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
divide = PARAMETER_HEADING_REGEX.split(docstring, 1)
|
||||||
|
if len(divide) == 1:
|
||||||
|
return docstring
|
||||||
|
|
||||||
|
description, param_docstring = divide
|
||||||
|
for match in NUMPY_DOCSTRING_ARG_REGEX.finditer(param_docstring):
|
||||||
|
name = match.group('name')
|
||||||
|
if name not in params:
|
||||||
|
continue
|
||||||
|
|
||||||
|
param = params[name]
|
||||||
|
if param.description is None:
|
||||||
|
param._description = _fold_text(match.group('description'))
|
||||||
|
|
||||||
|
return _fold_text(description.strip())
|
||||||
|
|
||||||
|
|
||||||
def wrap_callback(coro: Callable[P, Coro[T]], /) -> Callable[P, Coro[Optional[T]]]:
|
def wrap_callback(coro: Callable[P, Coro[T]], /) -> Callable[P, Coro[Optional[T]]]:
|
||||||
@functools.wraps(coro)
|
@functools.wraps(coro)
|
||||||
async def wrapped(*args: P.args, **kwargs: P.kwargs) -> Optional[T]:
|
async def wrapped(*args: P.args, **kwargs: P.kwargs) -> Optional[T]:
|
||||||
@ -365,9 +404,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
|||||||
if help_doc is not None:
|
if help_doc is not None:
|
||||||
help_doc = inspect.cleandoc(help_doc)
|
help_doc = inspect.cleandoc(help_doc)
|
||||||
else:
|
else:
|
||||||
help_doc = inspect.getdoc(func)
|
help_doc = extract_descriptions_from_docstring(func, self.params)
|
||||||
if isinstance(help_doc, bytes):
|
|
||||||
help_doc = help_doc.decode('utf-8')
|
|
||||||
|
|
||||||
self.help: Optional[str] = help_doc
|
self.help: Optional[str] = help_doc
|
||||||
|
|
||||||
|
131
tests/test_ext_commands_description.py
Normal file
131
tests/test_ext_commands_description.py
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
"""
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
to deal in the Software without restriction, including without limitation
|
||||||
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from discord.ext import commands
|
||||||
|
|
||||||
|
|
||||||
|
def test_ext_commands_descriptions_explicit():
|
||||||
|
@commands.command(help='This is the short description that will appear.')
|
||||||
|
async def describe(
|
||||||
|
ctx: commands.Context,
|
||||||
|
arg: str = commands.param(description='Description of arg.'),
|
||||||
|
arg2: int = commands.param(description='Description of arg2.'),
|
||||||
|
) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
assert describe.help == 'This is the short description that will appear.'
|
||||||
|
assert describe.clean_params['arg'].description == 'Description of arg.'
|
||||||
|
assert describe.clean_params['arg2'].description == 'Description of arg2.'
|
||||||
|
|
||||||
|
|
||||||
|
def test_ext_commands_descriptions_no_args():
|
||||||
|
@commands.command()
|
||||||
|
async def no_args(ctx: commands.Context) -> None:
|
||||||
|
"""This is the short description that will appear."""
|
||||||
|
|
||||||
|
assert no_args.help == 'This is the short description that will appear.'
|
||||||
|
|
||||||
|
|
||||||
|
def test_ext_commands_descriptions_numpy():
|
||||||
|
@commands.command()
|
||||||
|
async def numpy(ctx: commands.Context, arg: str, arg2: int) -> None:
|
||||||
|
"""This is the short description that will appear.
|
||||||
|
|
||||||
|
This extended description will also appear in the command description.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
arg: str
|
||||||
|
Docstring description of arg.
|
||||||
|
This is the second line of the arg docstring.
|
||||||
|
arg2: int
|
||||||
|
Docstring description of arg2.
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert (
|
||||||
|
numpy.help
|
||||||
|
== 'This is the short description that will appear.\nThis extended description will also appear in the command description.'
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
numpy.clean_params['arg'].description
|
||||||
|
== 'Docstring description of arg. This is the second line of the arg docstring.'
|
||||||
|
)
|
||||||
|
assert numpy.clean_params['arg2'].description == 'Docstring description of arg2.'
|
||||||
|
|
||||||
|
|
||||||
|
def test_ext_commands_descriptions_numpy_extras():
|
||||||
|
@commands.command()
|
||||||
|
async def numpy(ctx: commands.Context, arg: str, arg2: int) -> None:
|
||||||
|
"""This is the short description that will appear.
|
||||||
|
|
||||||
|
This extended description will also appear in the command description.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
ctx: commands.Context
|
||||||
|
The interaction object.
|
||||||
|
arg: str
|
||||||
|
Docstring description of arg.
|
||||||
|
This is the second line of the arg docstring.
|
||||||
|
arg2: int
|
||||||
|
Docstring description of arg2.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
NoneType
|
||||||
|
This function does not return anything.
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert (
|
||||||
|
numpy.help
|
||||||
|
== 'This is the short description that will appear.\nThis extended description will also appear in the command description.'
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
numpy.clean_params['arg'].description
|
||||||
|
== 'Docstring description of arg. This is the second line of the arg docstring.'
|
||||||
|
)
|
||||||
|
assert numpy.clean_params['arg2'].description == 'Docstring description of arg2.'
|
||||||
|
|
||||||
|
|
||||||
|
def test_ext_commands_descriptions_cog_commands():
|
||||||
|
class MyCog(commands.Cog):
|
||||||
|
@commands.command()
|
||||||
|
async def test(self, ctx: commands.Context, arg: str, arg2: int) -> None:
|
||||||
|
"""Test command
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
arg: str
|
||||||
|
Description of arg.
|
||||||
|
This is the second line of the arg description.
|
||||||
|
arg2: int
|
||||||
|
Description of arg2.
|
||||||
|
"""
|
||||||
|
|
||||||
|
cog = MyCog()
|
||||||
|
assert cog.test.help == 'Test command'
|
||||||
|
assert cog.test.clean_params['arg'].description == 'Description of arg. This is the second line of the arg description.'
|
||||||
|
assert cog.test.clean_params['arg2'].description == 'Description of arg2.'
|
Loading…
x
Reference in New Issue
Block a user