From 132200eed20cca15d60249d2614521e522467877 Mon Sep 17 00:00:00 2001 From: snoopuppy582 Date: Tue, 12 May 2026 23:57:38 +0900 Subject: [PATCH] feat: add ANSI background color helpers --- README.md | 23 +++++++++++++++++-- src/raztint/__init__.py | 33 +++++++++++++++++++++++++++ src/raztint/colors.py | 19 ++++++++++++++++ src/raztint/core.py | 15 ++++++++++++- tests/test_core.py | 50 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 137 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a8e5431..24393bb 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ that script, done right and fully tested. - Zero external dependencies (Python ≥ 3.10 standard library only) - Three-tiered icon fallback: Nerd Font → Unicode → ASCII, with environment-aware detection -- Full ANSI 16-color support for foreground text +- Full ANSI 16-color support for foreground and background text - Automatic TTY and Windows VT detection - Fully type-hinted public API - Support for text styles: bold, dim, italic, underline, and strikethrough @@ -105,7 +105,7 @@ You can import functions directly for quick usage, or instantiate the class for The easiest way to use RazTint is importing the pre-instantiated helpers: ```python -from raztint import green, red, ok, err, info, warn, bold, underline +from raztint import bg_blue, green, red, ok, err, info, warn, bold, underline # Coloring text print(green("Success! The operation completed.")) @@ -114,6 +114,7 @@ print(red("Critical Error: Database not found.")) # Styling text print(bold("This is bold text.")) print(underline(red("This is underlined red text."))) +print(red(bg_blue("This is red text on a blue background."))) # Using Icons (Auto-adapts to Nerd Font/Unicode/ASCII) print(f"{ok()} File saved successfully.") @@ -238,6 +239,24 @@ The following functions return strings wrapped with ANSI styling when supported: Internally, these use `tint.color()`. +### Background Color Functions + +The following functions return strings wrapped with ANSI background colors when supported: + +| Standard Backgrounds | Bright Variants | +|----------------------|------------------------------| +| `bg_black(text)` | `bg_gray(text)` | +| `bg_red(text)` | `bg_bright_red(text)` | +| `bg_green(text)` | `bg_bright_green(text)` | +| `bg_yellow(text)` | `bg_bright_yellow(text)` | +| `bg_blue(text)` | `bg_bright_blue(text)` | +| `bg_magenta(text)` | `bg_bright_magenta(text)` | +| `bg_cyan(text)` | `bg_bright_cyan(text)` | +| `bg_white(text)` | `bg_bright_white(text)` | + +Internally, these use `tint.background()` and reset with `\033[49m`, so nested +background colors do not clear an outer foreground color. + ### Text Style Functions In addition to colors, RazTint provides functions for applying text styles. These styles use their own reset codes, so applying a style won't remove any color you have already applied. diff --git a/src/raztint/__init__.py b/src/raztint/__init__.py index fb37c14..841608d 100644 --- a/src/raztint/__init__.py +++ b/src/raztint/__init__.py @@ -28,6 +28,23 @@ bright_cyan = tint.bright_cyan # type: ignore bright_white = tint.bright_white # type: ignore +bg_black = tint.bg_black # type: ignore +bg_red = tint.bg_red # type: ignore +bg_green = tint.bg_green # type: ignore +bg_yellow = tint.bg_yellow # type: ignore +bg_blue = tint.bg_blue # type: ignore +bg_magenta = tint.bg_magenta # type: ignore +bg_cyan = tint.bg_cyan # type: ignore +bg_white = tint.bg_white # type: ignore +bg_gray = tint.bg_gray # type: ignore +bg_bright_red = tint.bg_bright_red # type: ignore +bg_bright_green = tint.bg_bright_green # type: ignore +bg_bright_yellow = tint.bg_bright_yellow # type: ignore +bg_bright_blue = tint.bg_bright_blue # type: ignore +bg_bright_magenta = tint.bg_bright_magenta # type: ignore +bg_bright_cyan = tint.bg_bright_cyan # type: ignore +bg_bright_white = tint.bg_bright_white # type: ignore + bold = tint.bold # type: ignore dim = tint.dim # type: ignore italic = tint.italic # type: ignore @@ -57,6 +74,22 @@ "bright_magenta", "bright_cyan", "bright_white", + "bg_black", + "bg_red", + "bg_green", + "bg_yellow", + "bg_blue", + "bg_magenta", + "bg_cyan", + "bg_white", + "bg_gray", + "bg_bright_red", + "bg_bright_green", + "bg_bright_yellow", + "bg_bright_blue", + "bg_bright_magenta", + "bg_bright_cyan", + "bg_bright_white", "bold", "dim", "italic", diff --git a/src/raztint/colors.py b/src/raztint/colors.py index a11ada0..da4911c 100644 --- a/src/raztint/colors.py +++ b/src/raztint/colors.py @@ -16,3 +16,22 @@ "BRIGHT_CYAN": "96", "BRIGHT_WHITE": "97", } + +BACKGROUND_COLORS: dict[str, str] = { + "BG_BLACK": "40", + "BG_RED": "41", + "BG_GREEN": "42", + "BG_YELLOW": "43", + "BG_BLUE": "44", + "BG_MAGENTA": "45", + "BG_CYAN": "46", + "BG_WHITE": "47", + "BG_GRAY": "100", + "BG_BRIGHT_RED": "101", + "BG_BRIGHT_GREEN": "102", + "BG_BRIGHT_YELLOW": "103", + "BG_BRIGHT_BLUE": "104", + "BG_BRIGHT_MAGENTA": "105", + "BG_BRIGHT_CYAN": "106", + "BG_BRIGHT_WHITE": "107", +} diff --git a/src/raztint/core.py b/src/raztint/core.py index 110144f..4e39550 100644 --- a/src/raztint/core.py +++ b/src/raztint/core.py @@ -1,6 +1,6 @@ from collections.abc import Callable -from .colors import COLORS +from .colors import BACKGROUND_COLORS, COLORS from .env_detect import get_icon_mode, supports_color from .icons import ICONS from .styles import STYLES @@ -11,6 +11,7 @@ class RazTint: def __init__(self) -> None: self.colors = COLORS + self.backgrounds = BACKGROUND_COLORS self.icons = ICONS self.styles = STYLES @@ -20,6 +21,9 @@ def __init__(self) -> None: for name, code in self.colors.items(): setattr(self, name.lower(), self._make_color_func(code)) + for name, code in self.backgrounds.items(): + setattr(self, name.lower(), self._make_background_func(code)) + for name, (on, off) in self.styles.items(): setattr(self, name.lower(), self._make_style_func(on, off)) @@ -42,6 +46,9 @@ def _has_nerd_fonts() -> bool: def _make_color_func(self, code: str) -> Callable[[str], str]: return lambda text: self.color(text, code) + def _make_background_func(self, code: str) -> Callable[[str], str]: + return lambda text: self.background(text, code) + def _make_style_func(self, on: str, off: str) -> Callable[[str], str]: return lambda text: self.style(text, on, off) @@ -64,6 +71,12 @@ def color(self, text: str, fg_code: str) -> str: return text return f"\033[{fg_code}m{text}\033[0m" + def background(self, text: str, bg_code: str) -> str: + """Apply ANSI background color code to text.""" + if not self.use_color: + return text + return f"\033[{bg_code}m{text}\033[49m" + def style(self, text: str, on_code: str, off_code: str) -> str: """Apply ANSI style to text with style-specific reset. diff --git a/tests/test_core.py b/tests/test_core.py index 5049cef..3d069a5 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,6 +1,8 @@ import os from unittest import mock +from raztint import bg_blue, bg_red, tint +from raztint.colors import BACKGROUND_COLORS from raztint.core import RazTint from raztint.styles import STYLES @@ -30,6 +32,11 @@ def test_methods_existence(self): assert hasattr(raztint, "white") assert hasattr(raztint, "gray") + # Background colors + assert hasattr(raztint, "bg_red") + assert hasattr(raztint, "bg_blue") + assert hasattr(raztint, "bg_bright_white") + # Icons assert hasattr(raztint, "ok") assert hasattr(raztint, "err") @@ -54,6 +61,49 @@ def test_color_method_enabled(self): assert "test" in result assert "\033[0m" in result + def test_background_methods_exist(self): + """Test that dynamic background color methods are created.""" + raztint = RazTint() + for background_name in BACKGROUND_COLORS: + assert hasattr(raztint, background_name.lower()) + + def test_background_method_disabled(self): + """Test background method returns plain text when disabled.""" + raztint = RazTint() + raztint.set_color(False) + + assert raztint.background("test", "41") == "test" + assert raztint.bg_red("test") == "test" + + def test_background_method_enabled_uses_background_reset(self): + """Test background colors use ANSI background codes and reset 49.""" + raztint = RazTint() + raztint.set_color(True) + + assert raztint.bg_red("test") == "\033[41mtest\033[49m" + assert raztint.bg_gray("test") == "\033[100mtest\033[49m" + + def test_background_preserves_outer_foreground_color(self): + """Background reset should not clear an outer foreground color.""" + raztint = RazTint() + raztint.set_color(True) + + assert ( + raztint.red(raztint.bg_blue("test")) + == "\033[31m\033[44mtest\033[49m\033[0m" + ) + + def test_module_level_background_helpers(self): + """Module-level singleton exposes background helpers.""" + original_use_color = tint.use_color + tint.set_color(True) + + try: + assert bg_red("test") == "\033[41mtest\033[49m" + assert bg_blue("test") == "\033[44mtest\033[49m" + finally: + tint.set_color(original_use_color) + def test_env_no_color(self): """Test NO_COLOR environment variable.""" with mock.patch.dict(os.environ, {"NO_COLOR": "1"}):