import logging import pathlib import re import string from typing import Optional import discord from redbot.core import Config, commands from redbot.core.bot import Red from redbot.core.commands import Cog, PrivilegeLevel from redbot.core.data_manager import cog_data_path log = logging.getLogger("red.fox-v3.cogguide") def get_parent_tree(command: commands.Command): out = f"{command.name}" if command.parent: # out = f"{get_parent_tree(command.parent)}-" + out out = f"{'-'.join(command.full_parent_name.split())}-" + out return out def markdown_link_replace(match, starts_with_text=None): """Converts a markdown match to restructuredtext match""" text = match.group(1) url = match.group(2) if starts_with_text and url.startswith(starts_with_text): url = url[len(starts_with_text):] url = url[:url.find('.')] return f":ref:`{text} <{url}>`" return f"{text}: {url}" def prepare_description(command: commands.Command): description = command.description or command.help description = description.replace("`", "``") pattern = re.compile(r"\[(.+)]\s?\((https?:\/\/[\w\d.\/?=#]+)\)") description = pattern.sub(markdown_link_replace, description) return description class CogGuide(commands.Cog): """ Cog to create cog guides Dunno if this is a good idea but I did it. Sue me. """ def __init__(self, bot: Red): super().__init__() self.bot = bot self.config = Config.get_conf(self, identifier=6711110371117105100101, force_registration=True) default_global = {"starts_with": None} self.config.register_global(**default_global) async def red_delete_data_for_user(self, **kwargs): """Nothing to delete""" return @commands.group() async def cogguideset(self, ctx: commands.Context): """Base command for configuring cogguide""" pass @cogguideset.command(name="url") async def cogguideset_url(self, ctx: commands.Context, url: str): """Sets the url of your ReadTheDocs for creating references For example: 'https://docs.discord.red/en/stable/' for Red docs """ if not url: await self.config.starts_with.clear() else: await self.config.starts_with.set(url) await ctx.tick() @commands.command() async def allcogguides(self, ctx: commands.Context): """ Create a template ReStructuredText file for all loaded cogs. Results can be found in the cog data folder. Returns: tick() """ for cog_name, cog in self.bot.cogs.items(): await self.create_cog_guide(cog_name, cog) await ctx.tick() @commands.command() async def cogguide(self, ctx: commands.Context, camel_cog_name: str): """ Create a template ReStructuredText file for a given loaded cog. Result can be found in the cog data folder. Args: camel_cog_name: Returns: tick """ cog: Optional[Cog] = self.bot.get_cog(camel_cog_name) if cog is None: await ctx.send("No cog found with that name") return await self.create_cog_guide(camel_cog_name, cog) await ctx.tick() async def create_cog_guide(self, camel_cog_name, cog): path: pathlib.Path = cog_data_path(self) lower_cog_name = f"{camel_cog_name.lower()}" reference = f"_{camel_cog_name.lower()}" filename = f"{lower_cog_name}.rst" filepath = path / filename privilege_levels = { PrivilegeLevel.MOD: "|mod-lock|", PrivilegeLevel.ADMIN: "|admin-lock|", PrivilegeLevel.GUILD_OWNER: "|guildowner-lock|", PrivilegeLevel.BOT_OWNER: "|owner-lock|", } intro = f""".. {reference}: {'=' * len(camel_cog_name)} {camel_cog_name} {'=' * len(camel_cog_name)} This is the cog guide for the {lower_cog_name} cog. You will find detailed docs about usage and commands. ``[p]`` is considered as your prefix. .. note:: To use this cog, load it by typing this:: [p]load {'customcom' if lower_cog_name == 'customcommands' else lower_cog_name} .. _{lower_cog_name}-usage: ----- Usage ----- {cog.description if cog.description else "This is a general description of what the cog does. This should be a very basic explanation, addressing the core purpose of the cog. This is some additional information about what this cog can do. Try to answer *the* most frequently asked question."} """ cog_commands_intro = f""" .. {reference}-commands: -------- Commands -------- """ def get_command_rst(command: commands.Command): description = prepare_description(command) cog_command = f""" .. {reference}-command-{get_parent_tree(command)}: {'^' * len(command.qualified_name) if not command.parent else '"' * len(command.qualified_name)} {command.qualified_name} {'^' * len(command.qualified_name) if not command.parent else '"' * len(command.qualified_name)} """ if command.requires.privilege_level in privilege_levels: cog_command += f""" .. note:: {privilege_levels[command.requires.privilege_level]} """ cog_command += f""" **Syntax** .. code-block:: none [p]{command.qualified_name} {command.signature} **Description** {description} """ return cog_command cog_commands_list = [] for com in cog.walk_commands(): cog_commands_list.append(get_command_rst(com)) with filepath.open("w", encoding="utf-8") as f: f.write(intro) f.write(cog_commands_intro) f.writelines(cog_commands_list)