Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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."))
Expand All @@ -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.")
Expand Down Expand Up @@ -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.
Expand Down
33 changes: 33 additions & 0 deletions src/raztint/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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",
Expand Down
19 changes: 19 additions & 0 deletions src/raztint/colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
}
15 changes: 14 additions & 1 deletion src/raztint/core.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -11,6 +11,7 @@ class RazTint:

def __init__(self) -> None:
self.colors = COLORS
self.backgrounds = BACKGROUND_COLORS
self.icons = ICONS
self.styles = STYLES

Expand All @@ -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))

Expand All @@ -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)

Expand All @@ -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.

Expand Down
50 changes: 50 additions & 0 deletions tests/test_core.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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")
Expand All @@ -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"}):
Expand Down
Loading