From 46870ec4f87e59a10326d31918cc6ec70d5d42f0 Mon Sep 17 00:00:00 2001 From: electron271 <66094410+electron271@users.noreply.github.com> Date: Sat, 2 Aug 2025 22:41:21 -0500 Subject: [PATCH] refactor: move constants to assets/data/constants.toml --- assets/data/constants.toml | 71 +++++++++++++ tests/unit/tux/utils/test_constants.py | 74 +++++++++++--- tux/utils/constants.py | 133 +++++++++++-------------- 3 files changed, 187 insertions(+), 91 deletions(-) create mode 100644 assets/data/constants.toml diff --git a/assets/data/constants.toml b/assets/data/constants.toml new file mode 100644 index 000000000..79f6bdfa6 --- /dev/null +++ b/assets/data/constants.toml @@ -0,0 +1,71 @@ +# Bot Constants Configuration +# This file contains all the constants used throughout the Tux Discord bot + +[embed_colors] +DEFAULT = 16044058 +INFO = 12634869 +WARNING = 16634507 +ERROR = 16067173 +SUCCESS = 10407530 +POLL = 14724968 +CASE = 16217742 +NOTE = 16752228 + +[embed_icons] +DEFAULT = "https://i.imgur.com/owW4EZk.png" +INFO = "https://i.imgur.com/8GRtR2G.png" +SUCCESS = "https://i.imgur.com/JsNbN7D.png" +ERROR = "https://i.imgur.com/zZjuWaU.png" +CASE = "https://i.imgur.com/c43cwnV.png" +NOTE = "https://i.imgur.com/VqPFbil.png" +POLL = "https://i.imgur.com/pkPeG5q.png" +ACTIVE_CASE = "https://github.com/allthingslinux/tux/blob/main/assets/embeds/active_case.png?raw=true" +INACTIVE_CASE = "https://github.com/allthingslinux/tux/blob/main/assets/embeds/inactive_case.png?raw=true" +ADD = "https://github.com/allthingslinux/tux/blob/main/assets/emojis/added.png?raw=true" +REMOVE = "https://github.com/allthingslinux/tux/blob/main/assets/emojis/removed.png?raw=true" +BAN = "https://github.com/allthingslinux/tux/blob/main/assets/emojis/ban.png?raw=true" +JAIL = "https://github.com/allthingslinux/tux/blob/main/assets/emojis/jail.png?raw=true" +KICK = "https://github.com/allthingslinux/tux/blob/main/assets/emojis/kick.png?raw=true" +TIMEOUT = "https://github.com/allthingslinux/tux/blob/main/assets/emojis/timeout.png?raw=true" +WARN = "https://github.com/allthingslinux/tux/blob/main/assets/emojis/warn.png?raw=true" + +[embed_limits] +max_name_length = 256 +max_desc_length = 4096 +max_fields = 25 +total_max = 6000 +field_value_length = 1024 + +[discord_limits] +nickname_max_length = 32 +context_menu_name_length = 32 +slash_cmd_name_length = 32 +slash_cmd_max_desc_length = 100 +slash_cmd_max_options = 25 +slash_option_name_length = 100 + +[interaction_limits] +action_row_max_items = 5 +selects_max_options = 25 +select_max_name_length = 100 + +[defaults] +reason = "No reason provided" +delete_after = 30 + +[snippet_config] +max_name_length = 20 +allowed_chars_regex = "^[a-zA-Z0-9-]+$" +pagination_limit = 10 + +[afk_config] +prefix = "[AFK] " +truncation_suffix = "..." + +[eight_ball_config] +question_length_limit = 120 +response_wrap_width = 30 + +[bookmark_config] +add_emoji = "🔖" +remove_emoji = "🗑️" diff --git a/tests/unit/tux/utils/test_constants.py b/tests/unit/tux/utils/test_constants.py index fa4f405a1..7a01cfc62 100644 --- a/tests/unit/tux/utils/test_constants.py +++ b/tests/unit/tux/utils/test_constants.py @@ -8,15 +8,31 @@ class TestConstants: def test_embed_limits(self): """Test that embed limit constants are correctly defined.""" - assert Constants.EMBED_MAX_NAME_LENGTH == 256 - assert Constants.EMBED_MAX_DESC_LENGTH == 4096 - assert Constants.EMBED_MAX_FIELDS == 25 - assert Constants.EMBED_TOTAL_MAX == 6000 - assert Constants.EMBED_FIELD_VALUE_LENGTH == 1024 + assert CONST.EMBED_MAX_NAME_LENGTH == 256 + assert CONST.EMBED_MAX_DESC_LENGTH == 4096 + assert CONST.EMBED_MAX_FIELDS == 25 + assert CONST.EMBED_TOTAL_MAX == 6000 + assert CONST.EMBED_FIELD_VALUE_LENGTH == 1024 - def test_default_reason(self): - """Test that default reason is correctly defined.""" - assert Constants.DEFAULT_REASON == "No reason provided" + def test_discord_limits(self): + """Test Discord-related limit constants.""" + assert CONST.NICKNAME_MAX_LENGTH == 32 + assert CONST.CONTEXT_MENU_NAME_LENGTH == 32 + assert CONST.SLASH_CMD_NAME_LENGTH == 32 + assert CONST.SLASH_CMD_MAX_DESC_LENGTH == 100 + assert CONST.SLASH_CMD_MAX_OPTIONS == 25 + assert CONST.SLASH_OPTION_NAME_LENGTH == 100 + + def test_interaction_limits(self): + """Test interaction-related constants.""" + assert CONST.ACTION_ROW_MAX_ITEMS == 5 + assert CONST.SELECTS_MAX_OPTIONS == 25 + assert CONST.SELECT_MAX_NAME_LENGTH == 100 + + def test_default_values(self): + """Test default value constants.""" + assert CONST.DEFAULT_REASON == "No reason provided" + assert CONST.DEFAULT_DELETE_AFTER == 30 def test_const_instance(self): """Test that CONST is an instance of Constants.""" @@ -24,16 +40,44 @@ def test_const_instance(self): def test_snippet_constants(self): """Test snippet-related constants.""" - assert Constants.SNIPPET_MAX_NAME_LENGTH == 20 - assert Constants.SNIPPET_ALLOWED_CHARS_REGEX == r"^[a-zA-Z0-9-]+$" - assert Constants.SNIPPET_PAGINATION_LIMIT == 10 + assert CONST.SNIPPET_MAX_NAME_LENGTH == 20 + assert CONST.SNIPPET_ALLOWED_CHARS_REGEX == "^[a-zA-Z0-9-]+$" + assert CONST.SNIPPET_PAGINATION_LIMIT == 10 def test_afk_constants(self): """Test AFK-related constants.""" - assert Constants.AFK_PREFIX == "[AFK] " - assert Constants.AFK_TRUNCATION_SUFFIX == "..." + assert CONST.AFK_PREFIX == "[AFK] " + assert CONST.AFK_TRUNCATION_SUFFIX == "..." def test_eight_ball_constants(self): """Test 8ball-related constants.""" - assert Constants.EIGHT_BALL_QUESTION_LENGTH_LIMIT == 120 - assert Constants.EIGHT_BALL_RESPONSE_WRAP_WIDTH == 30 + assert CONST.EIGHT_BALL_QUESTION_LENGTH_LIMIT == 120 + assert CONST.EIGHT_BALL_RESPONSE_WRAP_WIDTH == 30 + + def test_bookmark_constants(self): + """Test bookmark-related constants.""" + assert CONST.ADD_BOOKMARK == "🔖" + assert CONST.REMOVE_BOOKMARK == "🗑️" + + def test_embed_colors_loaded(self): + """Test that embed colors are correctly loaded from TOML.""" + assert isinstance(CONST.EMBED_COLORS, dict) + assert "DEFAULT" in CONST.EMBED_COLORS + assert "INFO" in CONST.EMBED_COLORS + assert "ERROR" in CONST.EMBED_COLORS + assert isinstance(CONST.EMBED_COLORS["DEFAULT"], int) + + def test_embed_icons_loaded(self): + """Test that embed icons are correctly loaded from TOML.""" + assert isinstance(CONST.EMBED_ICONS, dict) + assert "DEFAULT" in CONST.EMBED_ICONS + assert "INFO" in CONST.EMBED_ICONS + assert "ERROR" in CONST.EMBED_ICONS + assert isinstance(CONST.EMBED_ICONS["DEFAULT"], str) + assert CONST.EMBED_ICONS["DEFAULT"].startswith("https://") + + def test_constants_file_loading(self): + """Test that creating a new Constants instance loads data correctly.""" + new_const = Constants() + assert new_const.DEFAULT_REASON == CONST.DEFAULT_REASON + assert new_const.EMBED_COLORS == CONST.EMBED_COLORS diff --git a/tux/utils/constants.py b/tux/utils/constants.py index ec81c7a3d..344bdabf9 100644 --- a/tux/utils/constants.py +++ b/tux/utils/constants.py @@ -1,83 +1,64 @@ -from typing import Final +import tomllib +from typing import Any, Final -# TODO: move to assets/data/ potentially +from tux.utils.config import workspace_root + + +def _load_constants() -> dict[str, Any]: + """Load constants from the TOML configuration file.""" + constants_path = workspace_root / "assets" / "data" / "constants.toml" + with constants_path.open("rb") as f: + return tomllib.load(f) class Constants: - # Color constants - EMBED_COLORS: Final[dict[str, int]] = { - "DEFAULT": 16044058, - "INFO": 12634869, - "WARNING": 16634507, - "ERROR": 16067173, - "SUCCESS": 10407530, - "POLL": 14724968, - "CASE": 16217742, - "NOTE": 16752228, - } - - # Icon constants - EMBED_ICONS: Final[dict[str, str]] = { - "DEFAULT": "https://i.imgur.com/owW4EZk.png", - "INFO": "https://i.imgur.com/8GRtR2G.png", - "SUCCESS": "https://i.imgur.com/JsNbN7D.png", - "ERROR": "https://i.imgur.com/zZjuWaU.png", - "CASE": "https://i.imgur.com/c43cwnV.png", - "NOTE": "https://i.imgur.com/VqPFbil.png", - "POLL": "https://i.imgur.com/pkPeG5q.png", - "ACTIVE_CASE": "https://github.com/allthingslinux/tux/blob/main/assets/embeds/active_case.png?raw=true", - "INACTIVE_CASE": "https://github.com/allthingslinux/tux/blob/main/assets/embeds/inactive_case.png?raw=true", - "ADD": "https://github.com/allthingslinux/tux/blob/main/assets/emojis/added.png?raw=true", - "REMOVE": "https://github.com/allthingslinux/tux/blob/main/assets/emojis/removed.png?raw=true", - "BAN": "https://github.com/allthingslinux/tux/blob/main/assets/emojis/ban.png?raw=true", - "JAIL": "https://github.com/allthingslinux/tux/blob/main/assets/emojis/jail.png?raw=true", - "KICK": "https://github.com/allthingslinux/tux/blob/main/assets/emojis/kick.png?raw=true", - "TIMEOUT": "https://github.com/allthingslinux/tux/blob/main/assets/emojis/timeout.png?raw=true", - "WARN": "https://github.com/allthingslinux/tux/blob/main/assets/emojis/warn.png?raw=true", - } - - # Embed limit constants - EMBED_MAX_NAME_LENGTH = 256 - EMBED_MAX_DESC_LENGTH = 4096 - EMBED_MAX_FIELDS = 25 - EMBED_TOTAL_MAX = 6000 - EMBED_FIELD_VALUE_LENGTH = 1024 - - NICKNAME_MAX_LENGTH = 32 - - # Interaction constants - ACTION_ROW_MAX_ITEMS = 5 - SELECTS_MAX_OPTIONS = 25 - SELECT_MAX_NAME_LENGTH = 100 - - # App commands constants - CONTEXT_MENU_NAME_LENGTH = 32 - SLASH_CMD_NAME_LENGTH = 32 - SLASH_CMD_MAX_DESC_LENGTH = 100 - SLASH_CMD_MAX_OPTIONS = 25 - SLASH_OPTION_NAME_LENGTH = 100 - - DEFAULT_REASON = "No reason provided" - - # Snippet constants - SNIPPET_MAX_NAME_LENGTH = 20 - SNIPPET_ALLOWED_CHARS_REGEX = r"^[a-zA-Z0-9-]+$" - SNIPPET_PAGINATION_LIMIT = 10 - - # Message timings - DEFAULT_DELETE_AFTER = 30 - - # AFK constants - AFK_PREFIX = "[AFK] " - AFK_TRUNCATION_SUFFIX = "..." - - # 8ball constants - EIGHT_BALL_QUESTION_LENGTH_LIMIT = 120 - EIGHT_BALL_RESPONSE_WRAP_WIDTH = 30 - - # Bookmark constants - ADD_BOOKMARK = "🔖" - REMOVE_BOOKMARK = "🗑️" + def __init__(self) -> None: + data = _load_constants() + + self.EMBED_COLORS: Final[dict[str, int]] = data["embed_colors"] + + self.EMBED_ICONS: Final[dict[str, str]] = data["embed_icons"] + + embed_limits: dict[str, Any] = data["embed_limits"] + self.EMBED_MAX_NAME_LENGTH: Final[int] = int(embed_limits["max_name_length"]) + self.EMBED_MAX_DESC_LENGTH: Final[int] = int(embed_limits["max_desc_length"]) + self.EMBED_MAX_FIELDS: Final[int] = int(embed_limits["max_fields"]) + self.EMBED_TOTAL_MAX: Final[int] = int(embed_limits["total_max"]) + self.EMBED_FIELD_VALUE_LENGTH: Final[int] = int(embed_limits["field_value_length"]) + + discord_limits: dict[str, Any] = data["discord_limits"] + self.NICKNAME_MAX_LENGTH: Final[int] = int(discord_limits["nickname_max_length"]) + self.CONTEXT_MENU_NAME_LENGTH: Final[int] = int(discord_limits["context_menu_name_length"]) + self.SLASH_CMD_NAME_LENGTH: Final[int] = int(discord_limits["slash_cmd_name_length"]) + self.SLASH_CMD_MAX_DESC_LENGTH: Final[int] = int(discord_limits["slash_cmd_max_desc_length"]) + self.SLASH_CMD_MAX_OPTIONS: Final[int] = int(discord_limits["slash_cmd_max_options"]) + self.SLASH_OPTION_NAME_LENGTH: Final[int] = int(discord_limits["slash_option_name_length"]) + + interaction_limits: dict[str, Any] = data["interaction_limits"] + self.ACTION_ROW_MAX_ITEMS: Final[int] = int(interaction_limits["action_row_max_items"]) + self.SELECTS_MAX_OPTIONS: Final[int] = int(interaction_limits["selects_max_options"]) + self.SELECT_MAX_NAME_LENGTH: Final[int] = int(interaction_limits["select_max_name_length"]) + + defaults: dict[str, Any] = data["defaults"] + self.DEFAULT_REASON: Final[str] = str(defaults["reason"]) + self.DEFAULT_DELETE_AFTER: Final[int] = int(defaults["delete_after"]) + + snippet_config: dict[str, Any] = data["snippet_config"] + self.SNIPPET_MAX_NAME_LENGTH: Final[int] = int(snippet_config["max_name_length"]) + self.SNIPPET_ALLOWED_CHARS_REGEX: Final[str] = str(snippet_config["allowed_chars_regex"]) + self.SNIPPET_PAGINATION_LIMIT: Final[int] = int(snippet_config["pagination_limit"]) + + afk_config: dict[str, Any] = data["afk_config"] + self.AFK_PREFIX: Final[str] = str(afk_config["prefix"]) + self.AFK_TRUNCATION_SUFFIX: Final[str] = str(afk_config["truncation_suffix"]) + + eight_ball_config: dict[str, Any] = data["eight_ball_config"] + self.EIGHT_BALL_QUESTION_LENGTH_LIMIT: Final[int] = int(eight_ball_config["question_length_limit"]) + self.EIGHT_BALL_RESPONSE_WRAP_WIDTH: Final[int] = int(eight_ball_config["response_wrap_width"]) + + bookmark_config: dict[str, Any] = data["bookmark_config"] + self.ADD_BOOKMARK: Final[str] = str(bookmark_config["add_emoji"]) + self.REMOVE_BOOKMARK: Final[str] = str(bookmark_config["remove_emoji"]) CONST = Constants()