Skip to content

Commit de29abb

Browse files
committed
feat(help): simplify select options creation by iterating over cached categories
fix(logging): enhance message handling for exceptions and newlines in LoguruRichHandler
1 parent 3c5db83 commit de29abb

File tree

2 files changed

+33
-31
lines changed

2 files changed

+33
-31
lines changed

Diff for: tux/help.py

+23-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import asyncio
22
import os
3-
import re
43
from collections.abc import Awaitable, Mapping
54
from pathlib import Path
65
from typing import Any, get_type_hints
@@ -103,17 +102,21 @@ def _add_command_field(embed: discord.Embed, command: commands.Command[Any, Any,
103102
async def _create_select_options(
104103
self,
105104
command_categories: dict[str, dict[str, str]],
106-
cog_groups: list[str],
107105
menu: ViewMenu,
108106
) -> dict[discord.SelectOption, list[Page]]:
107+
"""
108+
Creates select options for the help menu by iterating over the keys in the
109+
cached command categories. This approach ensures that even categories without
110+
a corresponding folder (e.g. "extra") are included.
111+
"""
109112
select_options: dict[discord.SelectOption, list[Page]] = {}
110-
111113
prefix: str = await self._get_prefix()
112114

115+
# Iterate over the keys in command_categories
113116
tasks: list[Awaitable[tuple[str, Page]]] = [
114117
self._create_page(cog_group, command_categories, menu, prefix)
115-
for cog_group in cog_groups
116-
if cog_group in command_categories and any(command_categories[cog_group].values())
118+
for cog_group in command_categories
119+
if any(command_categories[cog_group].values())
117120
]
118121

119122
select_options_data: list[tuple[str, Page]] = await asyncio.gather(*tasks)
@@ -132,6 +135,9 @@ async def _create_select_options(
132135
emoji = category_emoji_map.get(cog_group, "❓")
133136
select_options[discord.SelectOption(label=cog_group.capitalize(), emoji=emoji)] = [page]
134137

138+
logger.info(f"Select options: {select_options}")
139+
logger.info(f"Cached categories: {self._category_cache}")
140+
135141
return select_options
136142

137143
async def _create_page(
@@ -148,7 +154,6 @@ async def _create_page(
148154

149155
sorted_commands: list[tuple[str, str]] = sorted(command_categories[cog_group].items())
150156
description: str = "\n".join(f"**`{prefix}{cmd}`** | {command_list}" for cmd, command_list in sorted_commands)
151-
152157
embed.description = description
153158
page: Page = Page(embed=embed)
154159
menu.add_page(embed)
@@ -169,8 +174,8 @@ async def _add_cog_pages(
169174
) -> None:
170175
"""Adds pages for each cog category to the help menu."""
171176
command_categories = await self._get_command_categories(mapping)
172-
cog_groups = self._get_cog_groups()
173-
select_options = await self._create_select_options(command_categories, cog_groups, menu)
177+
# Instead of using filesystem folders, iterate over cached categories.
178+
select_options = await self._create_select_options(command_categories, menu)
174179
self._add_navigation_and_selection(menu, select_options)
175180

176181
async def _get_command_categories(
@@ -185,6 +190,7 @@ async def _get_command_categories(
185190

186191
for cog, mapping_commands in mapping.items():
187192
if cog and len(mapping_commands) > 0:
193+
# Attempt to extract the group using the cog's module name.
188194
cog_group = self._extract_cog_group(cog) or "extra"
189195
command_categories.setdefault(cog_group, {})
190196
for command in mapping_commands:
@@ -204,9 +210,15 @@ def _get_cog_groups() -> list[str]:
204210

205211
@staticmethod
206212
def _extract_cog_group(cog: commands.Cog) -> str | None:
207-
"""Extracts the cog group from a cog's string representation."""
208-
if match := re.search(r"<cogs\.([^\.]+)\..*>", str(cog)):
209-
return match[1]
213+
"""
214+
Extracts the cog group using the cog's module attribute.
215+
For example, if a cog's module is 'tux.cogs.admin.some_cog', this returns 'admin'.
216+
"""
217+
module = getattr(cog, "__module__", "")
218+
parts = module.split(".")
219+
# Assuming the structure is: tux.cogs.<group>...
220+
if len(parts) >= 3 and parts[1].lower() == "cogs":
221+
return parts[2].lower()
210222
return None
211223

212224
# Sending Help Messages

Diff for: tux/utils/logging.py

+10-20
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
from rich.text import Text
1818
from rich.theme import Theme
1919

20-
from tux.utils.config import Config
21-
2220
T = TypeVar("T")
2321

2422

@@ -73,6 +71,11 @@ def emit(self, record: LogRecord) -> None:
7371
# Get the formatted message from loguru's formatter.
7472
message = self.format(record)
7573

74+
# If there is exception info (or the message contains newlines),
75+
# delegate to the base class so that Rich can render tracebacks properly.
76+
if record.exc_info or "\n" in message:
77+
return super().emit(record)
78+
7679
# --- Time formatting ---
7780
time_format: str | Callable[[datetime], Text] | None = (
7881
None if self.formatter is None else self.formatter.datefmt
@@ -114,12 +117,6 @@ def emit(self, record: LogRecord) -> None:
114117

115118
# --- Build the continued prefix ---
116119
# We want the continued prefix to have the same plain-text width as the normal one.
117-
# The normal prefix width (plain) is:
118-
# len(symbol + " ") + (len(log_time_str) + 2) + (LEVEL_FIELD_WIDTH + 2)
119-
# For the continued prefix we print "CONTINUED" (padded) in a single bracket:
120-
# Total width = len(symbol + " ") + (continued_field_width + 2)
121-
# Setting these equal gives:
122-
# continued_field_width = len(log_time_str) + LEVEL_FIELD_WIDTH + 2
123120
continued_field_width = len(log_time_str) + level_field_width
124121
continued_prefix_markup = (
125122
f"{symbol} "
@@ -138,34 +135,27 @@ def emit(self, record: LogRecord) -> None:
138135
source_info_plain = Text.from_markup(source_info).plain
139136

140137
# --- Total width ---
141-
# Use the console's actual width if available.
142138
total_width = (self.console.size.width or self.console.width) or 80
143139

144140
# Convert the formatted message to plain text.
145141
plain_message = Text.from_markup(message).plain
146142

147143
# --- One-line vs two-line decision ---
148-
# For one-line messages, the available space is the total width
149-
# minus the widths of the normal prefix and the source info.
150144
available_for_message = total_width - len(first_prefix_plain) - len(source_info_plain)
151145
if len(plain_message) <= available_for_message:
152-
# The message fits on one line.
153146
padded_msg = plain_message.ljust(available_for_message)
154147
full_line = first_prefix_markup + padded_msg + source_info
155148
self.console.print(full_line, markup=True, highlight=False)
156149
else:
157-
# --- Two-line (continued) layout ---
158-
# First line: Reserve all space after the normal prefix.
150+
# Two-line (continued) layout
159151
first_line_area = total_width - len(first_prefix_plain)
160-
first_line_msg = plain_message[:first_line_area] # Simply cut off without ellipsis
152+
first_line_msg = plain_message[:first_line_area] # Cut off without ellipsis
161153

162-
# Second line: use the continued prefix and reserve space for the source info.
163154
second_line_area = total_width - len(continued_prefix_plain) - len(source_info_plain)
164-
# The remainder of the message is everything after what was printed on the first line.
165-
remainder_start = first_line_area # Adjusted to not account for ellipsis
155+
remainder_start = first_line_area
166156
second_line_msg = plain_message[remainder_start:]
167157
if len(second_line_msg) > second_line_area:
168-
second_line_msg = second_line_msg[:second_line_area] # Simply cut off without ellipsis
158+
second_line_msg = second_line_msg[:second_line_area] # Cut off without ellipsis
169159
padded_second_line_msg = second_line_msg.ljust(second_line_area)
170160
self.console.print(first_prefix_markup + first_line_msg, markup=True, highlight=False)
171161
self.console.print(
@@ -211,7 +201,7 @@ def setup_logging() -> None:
211201
highlighter=None,
212202
),
213203
"format": "{message}",
214-
"level": "DEBUG" if Config.DEV else "INFO",
204+
"level": "DEBUG",
215205
},
216206
],
217207
)

0 commit comments

Comments
 (0)