[V3 Docs] Docs for utils package (#1085)

* Docstrings for chat formatting

* Docstrings for mod utils

* Type checking

* Override CSS to highlight object name in definition

* More typing

* Utils docs pages

* Fix typo here
This commit is contained in:
Tobotimus 2017-11-20 09:14:05 +11:00 committed by palmtree5
parent b141729830
commit 5cef3e13e1
6 changed files with 361 additions and 27 deletions

11
docs/_static/theme_overrides.css vendored Normal file
View File

@ -0,0 +1,11 @@
/*
* This overrides the style for the path to each object definition, whilst
* the name of the object remains unchanged.
*
* e.g. in the definition `redbot.core.Config`, `redbot.core` is the
* desclassname.
*/
code.descclassname {
font-weight: normal !important;
}

View File

@ -103,6 +103,9 @@ html_theme = 'sphinx_rtd_theme'
# html_theme_options = {} # html_theme_options = {}
html_context = { html_context = {
'css_files': [
'_static/theme_overrides.css'
],
# Enable the "Edit in GitHub link within the header of each page. # Enable the "Edit in GitHub link within the header of each page.
'display_github': True, 'display_github': True,
'github_user': 'Cog-Creators', 'github_user': 'Cog-Creators',

17
docs/framework_utils.rst Normal file
View File

@ -0,0 +1,17 @@
.. red's core utils documentation
=================
Utility Functions
=================
Chat Formatting
===============
.. automodule:: redbot.core.utils.chat_formatting
:members:
Mod Helpers
===========
.. automodule:: redbot.core.utils.mod
:members:

View File

@ -37,6 +37,7 @@ Welcome to Red - Discord Bot's documentation!
framework_i18n framework_i18n
framework_modlog framework_modlog
framework_context framework_context
framework_utils

View File

@ -1,39 +1,145 @@
import itertools import itertools
from typing import List, Iterator
def error(text): def error(text: str) -> str:
"""Get text prefixed with an error emoji.
Returns
-------
str
The new message.
"""
return "\N{NO ENTRY SIGN} {}".format(text) return "\N{NO ENTRY SIGN} {}".format(text)
def warning(text): def warning(text: str) -> str:
"""Get text prefixed with a warning emoji.
Returns
-------
str
The new message.
"""
return "\N{WARNING SIGN} {}".format(text) return "\N{WARNING SIGN} {}".format(text)
def info(text): def info(text: str) -> str:
"""Get text prefixed with an info emoji.
Returns
-------
str
The new message.
"""
return "\N{INFORMATION SOURCE} {}".format(text) return "\N{INFORMATION SOURCE} {}".format(text)
def question(text): def question(text: str) -> str:
"""Get text prefixed with a question emoji.
Returns
-------
str
The new message.
"""
return "\N{BLACK QUESTION MARK ORNAMENT} {}".format(text) return "\N{BLACK QUESTION MARK ORNAMENT} {}".format(text)
def bold(text): def bold(text: str) -> str:
"""Get the given text in bold.
Parameters
----------
text : str
The text to be marked up.
Returns
-------
str
The marked up text.
"""
return "**{}**".format(text) return "**{}**".format(text)
def box(text, lang=""): def box(text: str, lang: str="") -> str:
"""Get the given text in a code block.
Parameters
----------
text : str
The text to be marked up.
lang : `str`, optional
The syntax highlighting language for the codeblock.
Returns
-------
str
The marked up text.
"""
ret = "```{}\n{}\n```".format(lang, text) ret = "```{}\n{}\n```".format(lang, text)
return ret return ret
def inline(text): def inline(text: str) -> str:
"""Get the given text as inline code.
Parameters
----------
text : str
The text to be marked up.
Returns
-------
str
The marked up text.
"""
return "`{}`".format(text) return "`{}`".format(text)
def italics(text): def italics(text: str) -> str:
"""Get the given text in italics.
Parameters
----------
text : str
The text to be marked up.
Returns
-------
str
The marked up text.
"""
return "*{}*".format(text) return "*{}*".format(text)
def bordered(text1: list, text2: list): def bordered(text1: List[str], text2: List[str]) -> str:
"""Get two blocks of text in a borders.
Note
----
This will only work with a monospaced font.
Parameters
----------
text1 : `list` of `str`
The 1st block of text, with each string being a new line.
text2 : `list` of `str`
The 2nd block of text. Should not be longer than ``text1``.
Returns
-------
str
The bordered text.
"""
width1, width2 = max((len(s1) + 9, len(s2) + 9) for s1 in text1 for s2 in text2) width1, width2 = max((len(s1) + 9, len(s2) + 9) for s1 in text1 for s2 in text2)
res = ['{}{}{}'.format(""*width1, " "*4, ""*width2)] res = ['{}{}{}'.format(""*width1, " "*4, ""*width2)]
flag = True flag = True
@ -50,9 +156,48 @@ def bordered(text1: list, text2: list):
return "\n".join(res) return "\n".join(res)
def pagify(text, delims=["\n"], *, priority=False, escape_mass_mentions=True, shorten_by=8, def pagify(text: str,
page_length=2000): delims: List[str]=["\n"],
"""DOES NOT RESPECT MARKDOWN BOXES OR INLINE CODE""" *,
priority: bool=False,
escape_mass_mentions: bool=True,
shorten_by: int=8,
page_length: int=2000) -> Iterator[str]:
"""Generate multiple pages from the given text.
Note
----
This does not respect code blocks or inline code.
Parameters
----------
text : str
The content to pagify and send.
delims : `list` of `str`, optional
Characters where page breaks will occur. If no delimiters are found
in a page, the page will break after ``page_length`` characters.
By default this only contains the newline.
Other Parameters
----------------
priority : `bool`
Set to :code:`True` to choose the page break delimiter based on the
order of ``delims``. Otherwise, the page will always break at the
last possible delimiter.
escape_mass_mentions : `bool`
If :code:`True`, any mass mentions (here or everyone) will be
silenced.
shorten_by : `int`
How much to shorten each page by. Defaults to 8.
page_length : `int`
The maximum length of each page. Defaults to 2000.
Yields
------
`str`
Pages of the given text.
"""
in_text = text in_text = text
page_length -= shorten_by page_length -= shorten_by
while len(in_text) > page_length: while len(in_text) > page_length:
@ -82,15 +227,59 @@ def pagify(text, delims=["\n"], *, priority=False, escape_mass_mentions=True, sh
yield in_text yield in_text
def strikethrough(text): def strikethrough(text: str) -> str:
"""Get the given text with a strikethrough.
Parameters
----------
text : str
The text to be marked up.
Returns
-------
str
The marked up text.
"""
return "~~{}~~".format(text) return "~~{}~~".format(text)
def underline(text): def underline(text: str) -> str:
"""Get the given text with an underline.
Parameters
----------
text : str
The text to be marked up.
Returns
-------
str
The marked up text.
"""
return "__{}__".format(text) return "__{}__".format(text)
def escape(text, *, mass_mentions=False, formatting=False): def escape(text: str, *, mass_mentions: bool=False,
formatting: bool=False) -> str:
"""Get text with all mass mentions or markdown escaped.
Parameters
----------
text : str
The text to be escaped.
mass_mentions : `bool`, optional
Set to :code:`True` to escape mass mentions in the text.
formatting : `bool`, optional
Set to :code:`True` to escpae any markdown formatting in the text.
Returns
-------
str
The escaped text.
"""
if mass_mentions: if mass_mentions:
text = text.replace("@everyone", "@\u200beveryone") text = text.replace("@everyone", "@\u200beveryone")
text = text.replace("@here", "@\u200bhere") text = text.replace("@here", "@\u200bhere")

View File

@ -1,5 +1,6 @@
import asyncio import asyncio
from typing import List from datetime import timedelta
from typing import List, Iterable, Union
import discord import discord
@ -9,6 +10,32 @@ from redbot.core.bot import Red
async def mass_purge(messages: List[discord.Message], async def mass_purge(messages: List[discord.Message],
channel: discord.TextChannel): channel: discord.TextChannel):
"""Bulk delete messages from a channel.
If more than 100 messages are supplied, the bot will delete 100 messages at
a time, sleeping between each action.
Note
----
Messages must not be older than 14 days, and the bot must not be a user
account.
Parameters
----------
messages : `list` of `discord.Message`
The messages to bulk delete.
channel : discord.TextChannel
The channel to delete messages from.
Raises
------
discord.Forbidden
You do not have proper permissions to delete the messages or youre not
using a bot account.
discord.HTTPException
Deleting the messages failed.
"""
while messages: while messages:
if len(messages) > 1: if len(messages) > 1:
await channel.delete_messages(messages[:100]) await channel.delete_messages(messages[:100])
@ -19,7 +46,17 @@ async def mass_purge(messages: List[discord.Message],
await asyncio.sleep(1.5) await asyncio.sleep(1.5)
async def slow_deletion(messages: List[discord.Message]): async def slow_deletion(messages: Iterable[discord.Message]):
"""Delete a list of messages one at a time.
Any exceptions raised when trying to delete the message will be silenced.
Parameters
----------
messages : `iterable` of `discord.Message`
The messages to delete.
"""
for message in messages: for message in messages:
try: try:
await message.delete() await message.delete()
@ -28,23 +65,62 @@ async def slow_deletion(messages: List[discord.Message]):
def get_audit_reason(author: discord.Member, reason: str = None): def get_audit_reason(author: discord.Member, reason: str = None):
"""Helper function to construct a reason to be provided """Construct a reason to appear in the audit log.
as the reason to appear in the audit log."""
Parameters
----------
author : discord.Member
The author behind the audit log action.
reason : str
The reason behidn the audit log action.
Returns
-------
str
The formatted audit log reason.
"""
return \ return \
"Action requested by {} (ID {}). Reason: {}".format(author, author.id, reason) if reason else \ "Action requested by {} (ID {}). Reason: {}".format(author, author.id, reason) if reason else \
"Action requested by {} (ID {}).".format(author, author.id) "Action requested by {} (ID {}).".format(author, author.id)
async def is_allowed_by_hierarchy( async def is_allowed_by_hierarchy(bot: Red,
bot: Red, settings: Config, server: discord.Guild, settings: Config,
mod: discord.Member, user: discord.Member): guild: discord.Guild,
if not await settings.guild(server).respect_hierarchy(): mod: discord.Member,
user: discord.Member):
if not await settings.guild(guild).respect_hierarchy():
return True return True
is_special = mod == server.owner or await bot.is_owner(mod) is_special = mod == guild.owner or await bot.is_owner(mod)
return mod.top_role.position > user.top_role.position or is_special return mod.top_role.position > user.top_role.position or is_special
async def is_mod_or_superior(bot: Red, obj: discord.Message or discord.Member or discord.Role): async def is_mod_or_superior(
bot: Red, obj: Union[discord.Message, discord.Member, discord.Role]):
"""Check if an object has mod or superior permissions.
If a message is passed, its author's permissions are checked. If a role is
passed, it simply checks if it is one of either the admin or mod roles.
Parameters
----------
bot : redbot.core.bot.Red
The bot object.
obj : `discord.Message` or `discord.Member` or `discord.Role`
The object to check permissions for.
Returns
-------
bool
:code:`True` if the object has mod permissions.
Raises
------
TypeError
If the wrong type of ``obj`` was passed.
"""
user = None user = None
if isinstance(obj, discord.Message): if isinstance(obj, discord.Message):
user = obj.author user = obj.author
@ -76,7 +152,20 @@ async def is_mod_or_superior(bot: Red, obj: discord.Message or discord.Member or
return False return False
def strfdelta(delta): def strfdelta(delta: timedelta):
"""Format a timedelta object to a message with time units.
Parameters
----------
delta : datetime.timedelta
The duration to parse.
Returns
-------
str
A message representing the timedelta with units.
"""
s = [] s = []
if delta.days: if delta.days:
ds = '%i day' % delta.days ds = '%i day' % delta.days
@ -97,7 +186,31 @@ def strfdelta(delta):
return ' '.join(s) return ' '.join(s)
async def is_admin_or_superior(bot: Red, obj: discord.Message or discord.Role or discord.Member): async def is_admin_or_superior(
bot: Red, obj: Union[discord.Message, discord.Member, discord.Role]):
"""Same as `is_mod_or_superior` except for admin permissions.
If a message is passed, its author's permissions are checked. If a role is
passed, it simply checks if it is the admin role.
Parameters
----------
bot : redbot.core.bot.Red
The bot object.
obj : `discord.Message` or `discord.Member` or `discord.Role`
The object to check permissions for.
Returns
-------
bool
:code:`True` if the object has admin permissions.
Raises
------
TypeError
If the wrong type of ``obj`` was passed.
"""
user = None user = None
if isinstance(obj, discord.Message): if isinstance(obj, discord.Message):
user = obj.author user = obj.author