mirror of
https://github.com/Rapptz/discord.py.git
synced 2025-06-07 20:28:38 +00:00
Parse command descriptions from docstrings
Co-authored-by: Danny <Rapptz@users.noreply.github.com>
This commit is contained in:
parent
1e4908b403
commit
7bf1a7483a
@ -131,15 +131,62 @@ CheckInputParameter = Union['Command[Any, ..., Any]', 'ContextMenu', CommandCall
|
|||||||
VALID_SLASH_COMMAND_NAME = re.compile(r'^[\w-]{1,32}$')
|
VALID_SLASH_COMMAND_NAME = re.compile(r'^[\w-]{1,32}$')
|
||||||
CAMEL_CASE_REGEX = re.compile(r'(?<!^)(?=[A-Z])')
|
CAMEL_CASE_REGEX = re.compile(r'(?<!^)(?=[A-Z])')
|
||||||
|
|
||||||
|
ARG_NAME_SUBREGEX = r'(?:\\?\*){0,2}(?P<name>\w+)'
|
||||||
|
|
||||||
|
ARG_DESCRIPTION_SUBREGEX = r'(?P<description>(?:.|\n)+?(?:\Z|\r?\n(?=[\S\r\n])))'
|
||||||
|
|
||||||
|
ARG_TYPE_SUBREGEX = r'(?:.+)'
|
||||||
|
|
||||||
|
GOOGLE_DOCSTRING_ARG_REGEX = re.compile(
|
||||||
|
rf'^{ARG_NAME_SUBREGEX}[ \t]*(?:\({ARG_TYPE_SUBREGEX}\))?[ \t]*:[ \t]*{ARG_DESCRIPTION_SUBREGEX}',
|
||||||
|
re.MULTILINE,
|
||||||
|
)
|
||||||
|
|
||||||
|
SPHINX_DOCSTRING_ARG_REGEX = re.compile(
|
||||||
|
rf'^:param {ARG_NAME_SUBREGEX}:[ \t]+{ARG_DESCRIPTION_SUBREGEX}',
|
||||||
|
re.MULTILINE,
|
||||||
|
)
|
||||||
|
|
||||||
|
NUMPY_DOCSTRING_ARG_REGEX = re.compile(
|
||||||
|
rf'^{ARG_NAME_SUBREGEX}(?:[ \t]*:)?(?:[ \t]+{ARG_TYPE_SUBREGEX})?[ \t]*\r?\n[ \t]+{ARG_DESCRIPTION_SUBREGEX}',
|
||||||
|
re.MULTILINE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _shorten(
|
def _shorten(
|
||||||
input: str,
|
input: str,
|
||||||
*,
|
*,
|
||||||
_wrapper: TextWrapper = TextWrapper(width=100, max_lines=1, replace_whitespace=True, placeholder='…'),
|
_wrapper: TextWrapper = TextWrapper(width=100, max_lines=1, replace_whitespace=True, placeholder='…'),
|
||||||
) -> str:
|
) -> str:
|
||||||
|
try:
|
||||||
|
# split on the first double newline since arguments may appear after that
|
||||||
|
input, _ = re.split(r'\n\s*\n', input, maxsplit=1)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
return _wrapper.fill(' '.join(input.strip().split()))
|
return _wrapper.fill(' '.join(input.strip().split()))
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_args_from_docstring(func: Callable[..., Any], params: Dict[str, CommandParameter]) -> Dict[str, str]:
|
||||||
|
docstring = inspect.getdoc(func)
|
||||||
|
|
||||||
|
if docstring is None:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Extract the arguments
|
||||||
|
# Note: These are loose regexes, but they are good enough for our purposes
|
||||||
|
# For Google-style, look only at the lines that are indented
|
||||||
|
section_lines = inspect.cleandoc('\n'.join(line for line in docstring.splitlines() if line.startswith(' ')))
|
||||||
|
docstring_styles = (
|
||||||
|
GOOGLE_DOCSTRING_ARG_REGEX.finditer(section_lines),
|
||||||
|
SPHINX_DOCSTRING_ARG_REGEX.finditer(docstring),
|
||||||
|
NUMPY_DOCSTRING_ARG_REGEX.finditer(docstring),
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
m.group('name'): m.group('description') for matches in docstring_styles for m in matches if m.group('name') in params
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _to_kebab_case(text: str) -> str:
|
def _to_kebab_case(text: str) -> str:
|
||||||
return CAMEL_CASE_REGEX.sub('-', text).lower()
|
return CAMEL_CASE_REGEX.sub('-', text).lower()
|
||||||
|
|
||||||
@ -223,7 +270,7 @@ def _populate_descriptions(params: Dict[str, CommandParameter], descriptions: Di
|
|||||||
if not isinstance(description, str):
|
if not isinstance(description, str):
|
||||||
raise TypeError('description must be a string')
|
raise TypeError('description must be a string')
|
||||||
|
|
||||||
param.description = description
|
param.description = _shorten(description)
|
||||||
|
|
||||||
if descriptions:
|
if descriptions:
|
||||||
first = next(iter(descriptions))
|
first = next(iter(descriptions))
|
||||||
@ -326,13 +373,15 @@ def _extract_parameters_from_callback(func: Callable[..., Any], globalns: Dict[s
|
|||||||
values = sorted(parameters, key=lambda a: a.required, reverse=True)
|
values = sorted(parameters, key=lambda a: a.required, reverse=True)
|
||||||
result = {v.name: v for v in values}
|
result = {v.name: v for v in values}
|
||||||
|
|
||||||
|
descriptions = _parse_args_from_docstring(func, result)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
descriptions = func.__discord_app_commands_param_description__
|
descriptions.update(func.__discord_app_commands_param_description__)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
for param in values:
|
for param in values:
|
||||||
if param.description is MISSING:
|
if param.description is MISSING:
|
||||||
param.description = '…'
|
param.description = '…'
|
||||||
else:
|
if descriptions:
|
||||||
_populate_descriptions(result, descriptions)
|
_populate_descriptions(result, descriptions)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user