thehelp.gradient
1import string 2from dataclasses import dataclass 3 4from rich.color import Color 5from typing_extensions import Self 6 7from .colormap import Tag 8 9 10@dataclass 11class RGB: 12 """ 13 Dataclass representing a 3 channel RGB color that is converted to a `rich` tag when casted to a string. 14 15 >>> color = RGB(100, 100, 100) 16 >>> str(color) 17 >>> "[rgb(100,100,100)]" 18 >>> from rich.console import Console 19 >>> console = Console() 20 >>> console.print(f"{color}Yeehaw") 21 22 Can also be initialized using a color name from https://rich.readthedocs.io/en/stable/appendix/colors.html 23 24 >>> color = RGB(name="magenta3") 25 >>> print(color) 26 >>> "[rgb(215,0,215)]" 27 28 Supports addition and subtraction of `RGB` objects as well as scalar multiplication and division. 29 30 >>> color1 = RGB(100, 100, 100) 31 >>> color2 = RGB(25, 50, 75) 32 >>> print(color1 + color2) 33 >>> "[rgb(125,150,175)]" 34 >>> print(color2 * 2) 35 >>> "[rgb(50,100,150)]" 36 """ 37 38 # Typing these as floats so `Gradient` can fractionally increment them 39 # When casted to a string, the values will be rounded to integers 40 r: float = 0 41 g: float = 0 42 b: float = 0 43 name: str = "" 44 45 def __post_init__(self): 46 if self.name: 47 self.r, self.g, self.b = Color.parse(self.name).get_truecolor() 48 49 def __str__(self) -> str: 50 return f"[rgb({round(self.r)},{round(self.g)},{round(self.b)})]" 51 52 def __sub__(self, other: Self) -> Self: 53 return self.__class__(self.r - other.r, self.g - other.g, self.b - other.b) 54 55 def __add__(self, other: Self) -> Self: 56 return self.__class__(self.r + other.r, self.g + other.g, self.b + other.b) 57 58 def __truediv__(self, val: float) -> Self: 59 return self.__class__(self.r / val, self.g / val, self.b / val) 60 61 def __mul__(self, val: float) -> Self: 62 return self.__class__(self.r * val, self.g * val, self.b * val) 63 64 def __eq__(self, other: Self) -> bool: 65 return all(getattr(self, c) == getattr(other, c) for c in "rgb") 66 67 68class Gradient: 69 """ 70 Apply a color gradient to strings when using `rich`. 71 72 When applied to a string, each character will increment in color from a start to a stop color. 73 74 Start and stop colors can be specified by either 75 a 3 tuple representing RGB values, 76 a `shortrich.Tag` object, 77 or a color name from https://rich.readthedocs.io/en/stable/appendix/colors.html. 78 79 Tuple: 80 >>> gradient = Gradient((255, 0, 0), (0, 255, 0)) 81 82 `shortrich.Tag`: 83 >>> colors = shortrich.ColorMap() 84 >>> gradient = Gradient(colors.red, colors.green) 85 86 Name: 87 >>> gradient = Gradient("red", "green") 88 89 Usage: 90 >>> from shortrich import Gradient 91 >>> from rich.console import Console 92 >>> console = Console() 93 >>> gradient = Gradient("red", "green") 94 >>> text = "Yeehaw" 95 >>> gradient_text = gradient.apply(text) 96 >>> # This produces: 97 >>> print(gradient_text) 98 >>> "[rgb(128,0,0)]Y[/][rgb(102,25,0)]e[/][rgb(76,51,0)]e[/][rgb(51,76,0)]h[/][rgb(25,102,0)]a[/][rgb(0,128,0)]w[/]" 99 >>> # When used with `console.print`, each character will be a different color 100 >>> console.print(gradient_text) 101 102 """ 103 104 def __init__( 105 self, 106 start: tuple[int, int, int] | str | Tag = "pink1", 107 stop: tuple[int, int, int] | str | Tag = "turquoise2", 108 ): 109 self._start = self._parse(start) 110 self._stop = self._parse(stop) 111 112 @property 113 def start(self) -> RGB: 114 """The starting color for the gradient.""" 115 return self._start 116 117 @start.setter 118 def start(self, color: str | Tag | tuple[int, int, int]): 119 self._start = self._parse(color) 120 121 @property 122 def stop(self) -> RGB: 123 """The ending color for the gradient.""" 124 return self._stop 125 126 @stop.setter 127 def stop(self, color: str | Tag | tuple[int, int, int]): 128 self._stop = self._parse(color) 129 130 @property 131 def valid_characters(self) -> str: 132 """Characters a color step can be applied to.""" 133 return string.ascii_letters + string.digits + string.punctuation 134 135 def _parse(self, color: str | Tag | tuple[int, int, int]) -> RGB: 136 if isinstance(color, str): 137 return RGB(name=color) 138 elif isinstance(color, Tag): 139 return RGB(name=color.name) 140 elif isinstance(color, tuple): 141 return RGB(*color) 142 raise ValueError(f"{color!r} is an invalid type.") 143 144 def _get_step_sizes(self, total_steps: int) -> RGB: 145 """Returns a `RGB` object representing the step size for each color channel.""" 146 return (self.stop - self.start) / total_steps 147 148 def _get_gradient_color(self, step: int, step_sizes: RGB) -> RGB: 149 """Returns a `RGB` object representing the color at `step`.""" 150 return self.start + (step_sizes * step) 151 152 def _get_num_steps(self, text: str) -> int: 153 """Returns the number of steps the gradient should be divided into.""" 154 return len([ch for ch in text if ch in self.valid_characters]) - 1 155 156 def apply(self, text: str) -> str: 157 """Apply the gradient to ascii letters, digits, and punctuation in `text`.""" 158 steps = self._get_num_steps(text) 159 step_sizes = self._get_step_sizes(steps) 160 gradient_text = "" 161 step = 0 162 for ch in text: 163 if ch in self.valid_characters: 164 gradient_text += f"{self._get_gradient_color(step, step_sizes)}{ch}[/]" 165 step += 1 166 else: 167 gradient_text += ch 168 return gradient_text
@dataclass
class
RGB:
11@dataclass 12class RGB: 13 """ 14 Dataclass representing a 3 channel RGB color that is converted to a `rich` tag when casted to a string. 15 16 >>> color = RGB(100, 100, 100) 17 >>> str(color) 18 >>> "[rgb(100,100,100)]" 19 >>> from rich.console import Console 20 >>> console = Console() 21 >>> console.print(f"{color}Yeehaw") 22 23 Can also be initialized using a color name from https://rich.readthedocs.io/en/stable/appendix/colors.html 24 25 >>> color = RGB(name="magenta3") 26 >>> print(color) 27 >>> "[rgb(215,0,215)]" 28 29 Supports addition and subtraction of `RGB` objects as well as scalar multiplication and division. 30 31 >>> color1 = RGB(100, 100, 100) 32 >>> color2 = RGB(25, 50, 75) 33 >>> print(color1 + color2) 34 >>> "[rgb(125,150,175)]" 35 >>> print(color2 * 2) 36 >>> "[rgb(50,100,150)]" 37 """ 38 39 # Typing these as floats so `Gradient` can fractionally increment them 40 # When casted to a string, the values will be rounded to integers 41 r: float = 0 42 g: float = 0 43 b: float = 0 44 name: str = "" 45 46 def __post_init__(self): 47 if self.name: 48 self.r, self.g, self.b = Color.parse(self.name).get_truecolor() 49 50 def __str__(self) -> str: 51 return f"[rgb({round(self.r)},{round(self.g)},{round(self.b)})]" 52 53 def __sub__(self, other: Self) -> Self: 54 return self.__class__(self.r - other.r, self.g - other.g, self.b - other.b) 55 56 def __add__(self, other: Self) -> Self: 57 return self.__class__(self.r + other.r, self.g + other.g, self.b + other.b) 58 59 def __truediv__(self, val: float) -> Self: 60 return self.__class__(self.r / val, self.g / val, self.b / val) 61 62 def __mul__(self, val: float) -> Self: 63 return self.__class__(self.r * val, self.g * val, self.b * val) 64 65 def __eq__(self, other: Self) -> bool: 66 return all(getattr(self, c) == getattr(other, c) for c in "rgb")
Dataclass representing a 3 channel RGB color that is converted to a rich
tag when casted to a string.
>>> color = RGB(100, 100, 100)
>>> str(color)
>>> "[rgb(100,100,100)]"
>>> from rich.console import Console
>>> console = Console()
>>> console.print(f"{color}Yeehaw")
Can also be initialized using a color name from https://rich.readthedocs.io/en/stable/appendix/colors.html
>>> color = RGB(name="magenta3")
>>> print(color)
>>> "[rgb(215,0,215)]"
Supports addition and subtraction of RGB
objects as well as scalar multiplication and division.
>>> color1 = RGB(100, 100, 100)
>>> color2 = RGB(25, 50, 75)
>>> print(color1 + color2)
>>> "[rgb(125,150,175)]"
>>> print(color2 * 2)
>>> "[rgb(50,100,150)]"
class
Gradient:
69class Gradient: 70 """ 71 Apply a color gradient to strings when using `rich`. 72 73 When applied to a string, each character will increment in color from a start to a stop color. 74 75 Start and stop colors can be specified by either 76 a 3 tuple representing RGB values, 77 a `shortrich.Tag` object, 78 or a color name from https://rich.readthedocs.io/en/stable/appendix/colors.html. 79 80 Tuple: 81 >>> gradient = Gradient((255, 0, 0), (0, 255, 0)) 82 83 `shortrich.Tag`: 84 >>> colors = shortrich.ColorMap() 85 >>> gradient = Gradient(colors.red, colors.green) 86 87 Name: 88 >>> gradient = Gradient("red", "green") 89 90 Usage: 91 >>> from shortrich import Gradient 92 >>> from rich.console import Console 93 >>> console = Console() 94 >>> gradient = Gradient("red", "green") 95 >>> text = "Yeehaw" 96 >>> gradient_text = gradient.apply(text) 97 >>> # This produces: 98 >>> print(gradient_text) 99 >>> "[rgb(128,0,0)]Y[/][rgb(102,25,0)]e[/][rgb(76,51,0)]e[/][rgb(51,76,0)]h[/][rgb(25,102,0)]a[/][rgb(0,128,0)]w[/]" 100 >>> # When used with `console.print`, each character will be a different color 101 >>> console.print(gradient_text) 102 103 """ 104 105 def __init__( 106 self, 107 start: tuple[int, int, int] | str | Tag = "pink1", 108 stop: tuple[int, int, int] | str | Tag = "turquoise2", 109 ): 110 self._start = self._parse(start) 111 self._stop = self._parse(stop) 112 113 @property 114 def start(self) -> RGB: 115 """The starting color for the gradient.""" 116 return self._start 117 118 @start.setter 119 def start(self, color: str | Tag | tuple[int, int, int]): 120 self._start = self._parse(color) 121 122 @property 123 def stop(self) -> RGB: 124 """The ending color for the gradient.""" 125 return self._stop 126 127 @stop.setter 128 def stop(self, color: str | Tag | tuple[int, int, int]): 129 self._stop = self._parse(color) 130 131 @property 132 def valid_characters(self) -> str: 133 """Characters a color step can be applied to.""" 134 return string.ascii_letters + string.digits + string.punctuation 135 136 def _parse(self, color: str | Tag | tuple[int, int, int]) -> RGB: 137 if isinstance(color, str): 138 return RGB(name=color) 139 elif isinstance(color, Tag): 140 return RGB(name=color.name) 141 elif isinstance(color, tuple): 142 return RGB(*color) 143 raise ValueError(f"{color!r} is an invalid type.") 144 145 def _get_step_sizes(self, total_steps: int) -> RGB: 146 """Returns a `RGB` object representing the step size for each color channel.""" 147 return (self.stop - self.start) / total_steps 148 149 def _get_gradient_color(self, step: int, step_sizes: RGB) -> RGB: 150 """Returns a `RGB` object representing the color at `step`.""" 151 return self.start + (step_sizes * step) 152 153 def _get_num_steps(self, text: str) -> int: 154 """Returns the number of steps the gradient should be divided into.""" 155 return len([ch for ch in text if ch in self.valid_characters]) - 1 156 157 def apply(self, text: str) -> str: 158 """Apply the gradient to ascii letters, digits, and punctuation in `text`.""" 159 steps = self._get_num_steps(text) 160 step_sizes = self._get_step_sizes(steps) 161 gradient_text = "" 162 step = 0 163 for ch in text: 164 if ch in self.valid_characters: 165 gradient_text += f"{self._get_gradient_color(step, step_sizes)}{ch}[/]" 166 step += 1 167 else: 168 gradient_text += ch 169 return gradient_text
Apply a color gradient to strings when using rich
.
When applied to a string, each character will increment in color from a start to a stop color.
Start and stop colors can be specified by either
a 3 tuple representing RGB values,
a shortrich.Tag
object,
or a color name from https://rich.readthedocs.io/en/stable/appendix/colors.html.
Tuple:
>>> gradient = Gradient((255, 0, 0), (0, 255, 0))
shortrich.Tag
:
>>> colors = shortrich.ColorMap()
>>> gradient = Gradient(colors.red, colors.green)
Name:
>>> gradient = Gradient("red", "green")
Usage:
>>> from shortrich import Gradient
>>> from rich.console import Console
>>> console = Console()
>>> gradient = Gradient("red", "green")
>>> text = "Yeehaw"
>>> gradient_text = gradient.apply(text)
>>> # This produces:
>>> print(gradient_text)
>>> "[rgb(128,0,0)]Y[/][rgb(102,25,0)]e[/][rgb(76,51,0)]e[/][rgb(51,76,0)]h[/][rgb(25,102,0)]a[/][rgb(0,128,0)]w[/]"
>>> # When used with `console.print`, each character will be a different color
>>> console.print(gradient_text)
Gradient( start: tuple[int, int, int] | str | thehelp.colormap.Tag = 'pink1', stop: tuple[int, int, int] | str | thehelp.colormap.Tag = 'turquoise2')
def
apply(self, text: str) -> str:
157 def apply(self, text: str) -> str: 158 """Apply the gradient to ascii letters, digits, and punctuation in `text`.""" 159 steps = self._get_num_steps(text) 160 step_sizes = self._get_step_sizes(steps) 161 gradient_text = "" 162 step = 0 163 for ch in text: 164 if ch in self.valid_characters: 165 gradient_text += f"{self._get_gradient_color(step, step_sizes)}{ch}[/]" 166 step += 1 167 else: 168 gradient_text += ch 169 return gradient_text
Apply the gradient to ascii letters, digits, and punctuation in text
.