Skip to content

Commit 4f80830

Browse files
Add a provisional API for replacing the help formatter (Cog-Creators#4011)
* Adds an API for replacing the help formatter * Apply suggestions from code review Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com> * add note about provisionality Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com>
1 parent 6cef336 commit 4f80830

4 files changed

Lines changed: 177 additions & 23 deletions

File tree

docs/framework_commands.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,17 @@ extend functionalities used throughout the bot, as outlined below.
4444
:no-undoc-members:
4545

4646
.. autoclass:: APIToken
47+
48+
******************
49+
Help Functionality
50+
******************
51+
52+
.. warning::
53+
54+
The content in this section is provisional and may change
55+
without prior notice or warning. Updates to this will be communicated
56+
on `this issue <https://github.com/Cog-Creators/Red-DiscordBot/issues/4084>`_
57+
58+
59+
.. automodule:: redbot.core.commands.help
60+
:members:

redbot/core/bot.py

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,61 @@ async def prefix_manager(bot, message) -> List[str]:
208208

209209
self._deletion_requests: MutableMapping[int, asyncio.Lock] = weakref.WeakValueDictionary()
210210

211+
def set_help_formatter(self, formatter: commands.help.HelpFormatterABC):
212+
"""
213+
Set's Red's help formatter.
214+
215+
.. warning::
216+
This method is provisional.
217+
218+
219+
The formatter must implement all methods in
220+
``commands.help.HelpFormatterABC``
221+
222+
Cogs which set a help formatter should inform users of this.
223+
Users should not use multiple cogs which set a help formatter.
224+
225+
This should specifically be an instance of a formatter.
226+
This allows cogs to pass a config object or similar to the
227+
formatter prior to the bot using it.
228+
229+
See ``commands.help.HelpFormatterABC`` for more details.
230+
231+
Raises
232+
------
233+
RuntimeError
234+
If the default formatter has already been replaced
235+
TypeError
236+
If given an invalid formatter
237+
"""
238+
239+
if not isinstance(formatter, commands.help.HelpFormatterABC):
240+
raise TypeError(
241+
"Help formatters must inherit from `commands.help.HelpFormatterABC` "
242+
"and implement the required interfaces."
243+
)
244+
245+
# do not switch to isinstance, we want to know that this has not been overriden,
246+
# even with a subclass.
247+
if type(self._help_formatter) is commands.help.RedHelpFormatter:
248+
self._help_formatter = formatter
249+
else:
250+
raise RuntimeError("The formatter has already been overriden.")
251+
252+
def reset_help_formatter(self):
253+
"""
254+
Resets Red's help formatter.
255+
256+
.. warning::
257+
This method is provisional.
258+
259+
260+
This exists for use in ``cog_unload`` for cogs which replace the formatter
261+
as well as for a rescue command in core_commands.
262+
263+
"""
264+
self._help_formatter = commands.help.RedHelpFormatter()
265+
211266
def get_command(self, name: str) -> Optional[commands.Command]:
212267
com = super().get_command(name)
213268
assert com is None or isinstance(com, commands.Command)
@@ -786,12 +841,18 @@ async def start(self, *args, **kwargs):
786841
return await super().start(*args, **kwargs)
787842

788843
async def send_help_for(
789-
self, ctx: commands.Context, help_for: Union[commands.Command, commands.GroupMixin, str]
844+
self,
845+
ctx: commands.Context,
846+
help_for: Union[commands.Command, commands.GroupMixin, str],
847+
*,
848+
from_help_command: bool = False,
790849
):
791850
"""
792851
Invokes Red's helpformatter for a given context and object.
793852
"""
794-
return await self._help_formatter.send_help(ctx, help_for)
853+
return await self._help_formatter.send_help(
854+
ctx, help_for, from_help_command=from_help_command
855+
)
795856

796857
async def embed_requested(self, channel, user, command=None) -> bool:
797858
"""

redbot/core/commands/help.py

Lines changed: 72 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
# Warning: The implementation below touches several private attributes.
2-
# While this implementation will be updated, and public interfaces maintained, derived classes
3-
# should not assume these private attributes are version safe, and use the provided HelpSettings
4-
# class for these settings.
2+
# While this implementation will be updated, and public interfaces maintained,
3+
# derived classes should not assume these private attributes are version safe,
4+
# and use the provided HelpSettings class for these settings.
55

66
# This is a full replacement of discord.py's help command
77
#
8-
# At a later date, there should be things added to support extra formatter
9-
# registration from 3rd party cogs.
10-
#
118
# This exists due to deficiencies in discord.py which conflict
129
# with our needs for per-context help settings
1310
# see https://github.com/Rapptz/discord.py/issues/2123
@@ -30,8 +27,7 @@
3027
# Additionally, this gives our users a bit more customization options including by
3128
# 3rd party cogs down the road.
3229

33-
# Note: 3rd party help must not remove the copyright notice
34-
30+
import abc
3531
import asyncio
3632
from collections import namedtuple
3733
from dataclasses import dataclass
@@ -48,7 +44,7 @@
4844
from ..utils._internal_utils import fuzzy_command_search, format_fuzzy_results
4945
from ..utils.chat_formatting import box, pagify
5046

51-
__all__ = ["red_help", "RedHelpFormatter", "HelpSettings"]
47+
__all__ = ["red_help", "RedHelpFormatter", "HelpSettings", "HelpFormatterABC"]
5248

5349
_ = Translator("Help", __file__)
5450

@@ -65,6 +61,11 @@
6561
class HelpSettings:
6662
"""
6763
A representation of help settings.
64+
65+
.. warning::
66+
67+
This class is provisional.
68+
6869
"""
6970

7071
page_char_limit: int = 1000
@@ -78,8 +79,10 @@ class HelpSettings:
7879

7980
# Contrib Note: This is intentional to not accept the bot object
8081
# There are plans to allow guild and user specific help settings
81-
# Adding a non-context based method now would involve a breaking change later.
82-
# At a later date, more methods should be exposed for non-context based creation.
82+
# Adding a non-context based method now would involve a breaking
83+
# change later.
84+
# At a later date, more methods should be exposed for
85+
# non-context based creation.
8386
#
8487
# This is also why we aren't just caching the
8588
# current state of these settings on the bot object.
@@ -102,23 +105,72 @@ def __init__(self, *, last, not_found):
102105
self.not_found = not_found
103106

104107

105-
class RedHelpFormatter:
108+
class HelpFormatterABC(abc.ABC):
109+
"""
110+
Describes the required interface of a help formatter.
111+
112+
Additional notes for 3rd party developers are included in this class.
113+
114+
.. note::
115+
You may define __init__ however you want
116+
(such as to include config),
117+
Red will not initialize a formatter for you,
118+
and must be passed an initialized formatter.
119+
120+
If you want to use Red's existing settings, use ``HelpSettings.from_context``
121+
122+
.. warning::
123+
124+
This class is documented but provisional with expected changes.
125+
126+
In the future, this class will receive changes to support
127+
invoking the help command without context.
128+
"""
129+
130+
@abc.abstractmethod
131+
async def send_help(
132+
self, ctx: Context, help_for: HelpTarget = None, *, from_help_command: bool = False
133+
):
134+
"""
135+
This is (currently) the only method you must implement.
136+
137+
This method should handle any and all errors which may arise.
138+
139+
The types subclasses must handle are defined as ``HelpTarget``
140+
"""
141+
...
142+
143+
144+
class RedHelpFormatter(HelpFormatterABC):
106145
"""
107146
Red's help implementation
108147
109148
This is intended to be overridable in parts to only change some behavior.
110149
111-
While currently, there is a global formatter, later plans include a context specific
112-
formatter selector as well as an API for cogs to register/un-register a formatter with the bot.
150+
While this exists as a class for easy partial overriding,
151+
most implementations should not need or want a shared state.
152+
153+
.. warning::
113154
114-
When implementing your own formatter, at minimum you must provide an implementation of
115-
`send_help` with identical signature.
155+
This class is documented but may receive changes between
156+
versions without warning as needed.
157+
The supported way to modify help is to write a separate formatter.
116158
117-
While this exists as a class for easy partial overriding, most implementations
118-
should not need or want a shared state.
159+
The primary reason for this class being documented is to allow
160+
the opaque use of the class as a fallback, as any method in base
161+
class which is intended for use will be present and implemented here.
162+
163+
.. note::
164+
165+
This class may use various internal methods which are not safe to
166+
use in third party code.
167+
The internal methods used here may change,
168+
with this class being updated at the same time.
119169
"""
120170

121-
async def send_help(self, ctx: Context, help_for: HelpTarget = None):
171+
async def send_help(
172+
self, ctx: Context, help_for: HelpTarget = None, *, from_help_command: bool = False
173+
):
122174
"""
123175
This delegates to other functions.
124176
@@ -724,4 +776,4 @@ async def red_help(ctx: Context, *, thing_to_get_help_for: str = None):
724776
(Help) you know I need someone
725777
(Help!)
726778
"""
727-
await ctx.bot.send_help_for(ctx, thing_to_get_help_for)
779+
await ctx.bot.send_help_for(ctx, thing_to_get_help_for, from_help_command=True)

redbot/core/core_commands.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
from . import (
2828
__version__,
2929
version_info as red_version_info,
30-
VersionInfo,
3130
checks,
3231
commands,
3332
errors,
@@ -2080,6 +2079,34 @@ async def helpset(self, ctx: commands.Context):
20802079
"""Manage settings for the help command."""
20812080
pass
20822081

2082+
@helpset.command(name="resetformatter")
2083+
async def helpset_resetformatter(self, ctx: commands.Context):
2084+
""" This resets [botname]'s help formatter to the default formatter """
2085+
2086+
ctx.bot.reset_help_formatter()
2087+
await ctx.send(
2088+
_(
2089+
"The help formatter has been reset. "
2090+
"This will not prevent cogs from modifying help, "
2091+
"you may need to remove a cog if this has been an issue."
2092+
)
2093+
)
2094+
2095+
@helpset.command(name="resetsettings")
2096+
async def helpset_resetsettings(self, ctx: commands.Context):
2097+
"""
2098+
This resets [botname]'s help settings to their defaults.
2099+
2100+
This may not have an impact when using custom formatters from 3rd party cogs
2101+
"""
2102+
await ctx.bot._config.help.clear()
2103+
await ctx.send(
2104+
_(
2105+
"The help settings have been reset to their defaults. "
2106+
"This may not have an impact when using 3rd party help formatters."
2107+
)
2108+
)
2109+
20832110
@helpset.command(name="usemenus")
20842111
async def helpset_usemenus(self, ctx: commands.Context, use_menus: bool = None):
20852112
"""

0 commit comments

Comments
 (0)