Skip to content

Text

Bases: Text

A rich text class that supports gradient colors and styles.

Source code in src/rich_gradient/text.py
class Text(RichText):
    """A rich text class that supports gradient colors and styles."""

    def __init__(
        self,
        text: TextType = "",
        colors: Optional[Sequence[ColorType]] = None,
        hues: int = 5,
        rainbow: bool = False,
        style: StyleType = "",
        justify: JustifyMethod = "default",
        overflow: OverflowMethod = "fold",
        no_wrap: bool = False,
        end: str = "\n",
        tab_size: int = 4,
        bg_colors: Optional[Sequence[ColorType]] = None,
        markup: bool = True,
        spans: Optional[Sequence[Span]] = None,
    ):
        """Initialize the Text with gradient colors and styles.
        Args:
            text (TextType): The text content.
            colors (Optional[List[ColorType]]): A list of colors as Color instances or strings.
            rainbow (bool): If True, generate a rainbow spectrum.
            hues (int): The number of hues to generate if colors are not provided.
            style (StyleType): The style of the text.
            justify (JustifyMethod): Justification method for the text. Defaults to `default`.
            overflow (OverflowMethod): Overflow method for the text. Defaults to `fold`.
            no_wrap (bool): If True, disable wrapping of the text. Defaults to False.
            end (str): The string to append at the end of the text. Default is a newline (`\\n`).
            tab_size (int): The number of spaces for a tab character. Defaults to 4.
            bg_colors (Optional[List[ColorType]]): A list of background colors as Color \
instances. Defaults to None.
            markup (bool): If True, parse Rich markup tags in the input text. Defaults to True.
            spans (Optional[Sequence[Span]]): A list of spans to apply to the text. \
            Defaults to None.
        """

        # Parse the input text with or without markup
        if markup:
            parsed_text = RichText.from_markup(
                text=str(text), style=style, justify=justify, overflow=overflow
            )
        else:
            parsed_text = RichText(
                strip_control_codes(str(text)),
                style=style,
                justify=justify,
                overflow=overflow,
            )

        # Extract parsed attributes
        plain = parsed_text.plain
        parsed_justify = parsed_text.justify
        parsed_overflow = parsed_text.overflow
        parsed_spans = parsed_text._spans

        # Initialize the parent class
        super().__init__(
            plain,
            style=style,
            justify=parsed_justify,
            overflow=parsed_overflow,
            no_wrap=no_wrap,
            end=end,
            tab_size=tab_size,
            spans=parsed_spans,
        )
        self._interpolate_bg_colors = False  # Ensure flag is always initialized
        # Normalize color inputs into rich.color.Color instances
        self.colors = self.parse_colors(colors, hues, rainbow)
        self.bg_colors = self.parse_bg_colors(bg_colors, hues)

        # Handle the single-color and single-background case: apply style directly and return early
        if len(self.colors) == 1 and len(self.bg_colors) == 1:
            # Apply the single color style directly, honoring a missing style definition.
            if isinstance(style, Style):
                base_style = style
            elif isinstance(style, str) and style:
                base_style = Style.parse(style)
            else:
                base_style = Style.null()
            color_layer = Style(color=self.colors[0], bgcolor=self.bg_colors[0])
            style_with_color = color_layer + base_style
            for index in range(len(self.plain)):
                self.stylize(style_with_color, index, index + 1)
            return

        # Apply the gradient coloring
        self.apply_gradient()

    @property
    def colors(self) -> list[Color]:
        """Return the list of colors in the gradient."""
        return list(self._colors) if self._colors else []

    @colors.setter
    def colors(self, value: Optional[Sequence[Color]]) -> None:
        """Set the list of colors in the gradient."""
        self._colors = list(value) if value else []

    @property
    def bg_colors(self) -> list[Color]:
        """Return the list of background colors in the gradient."""
        return list(self._bg_colors) if self._bg_colors else []

    @bg_colors.setter
    def bg_colors(self, value: Optional[Sequence[Color]]) -> None:
        """Set the list of background colors in the gradient."""
        self._bg_colors = list(value) if value else []

    @staticmethod
    def _normalize_color(value: ColorType) -> Color:
        """Normalize a single color-like value to a rich.color.Color.
        Accepts: Color, ColorTriplet, 3-tuple of ints, or string parsable
        by Color.parse. Note that rich-color-ext expands what is considered
        a valid color input.

        Args:
            value (ColorType): The color-like value to normalize.
        Returns:
            Color: The normalized Color instance.
        Raises:
            ColorParseError: If the color value cannot be parsed.
        """
        try:
            if isinstance(value, Color):
                return value
            elif isinstance(value, ColorTriplet):
                return Color.from_rgb(value.red, value.green, value.blue)
            elif isinstance(value, tuple) and len(value) == 3:
                r, g, b = value
                return Color.from_rgb(int(r), int(g), int(b))
            elif isinstance(value, str):
                return Color.parse(value)
            else:
                # Reject unsupported types explicitly (e.g., int, None)
                raise TypeError(f"Unsupported color type: {type(value)!r}")
        except ColorParseError as cpe:
            raise ColorParseError(
                f"Failed to parse and normalize color: {value}"
            ) from cpe

    @staticmethod
    def parse_colors(
        colors: Optional[Sequence[ColorType]] = None,
        hues: int = 5,
        rainbow: bool = False,
    ) -> List[Color]:
        """Parse and return a list of colors for the gradient.
        Supports:
        - rgb colors (e.g. `'rgb(255, 0, 0)'`)
        - rgb tuples (e.g., `(255, 0, 0)`)
        - 3-digit hex colors (e.g., `'#f00'`, `'#F90'`)
        - 6-digit hex colors (e.g., `'#ff0000'`, `'#00FF00'`)
        - CSS names (e.g., `'red'`, `'aliceblue'`)
        - rich.color.Color objects (e.g., `Color.parse('#FF0000')`)
        Args:
            colors (Optional[Sequence[ColorType | Color]]): A list of colors as Color
                instances, tuples of integers, or strings.
            hues (int): The number of hues to generate if colors are not provided. Defaults to 5.
            rainbow (bool): Whether to generate a rainbow spectrum. Note that rainbow overrides
                any colors or hues provided. Defaults to False
        Raises:
            ColorParseError: If any color value cannot be parsed.
            ValueError: If no colors are provided, rainbow is False, and hues < 2.
        Returns:
            List[rich.color.Color]: A list of Color objects.
        """
        # When rainbow is True, we use a full 17-color spectrum
        if rainbow:
            return Spectrum(hues=17).colors

        # If no colors are provided, fall back to Spectrum with the specified hues
        if colors is None or len(colors) == 0:
            if hues < 2:
                raise ValueError(
                    f"If `rainbow=False` and no colors are provided, hues must be \
at least 2. Invalid hues value: {hues}"
                )
            return Spectrum(hues).colors

        # If we have colors, parse and normalize them
        parsed: List[Color] = []
        for c in colors:
            try:
                parsed.append(Text._normalize_color(c))
            except Exception as exc:  # pragma: no cover - defensive
                raise ColorParseError(f"Unsupported color value: {c}") from exc
        return parsed

    def parse_bg_colors(
        self, bg_colors: Optional[Sequence[ColorType]] = None, hues: int = 5
    ) -> List[Color]:
        """Parse and return a list of background colors for the gradient.
        Supports 3-digit hex colors (e.g., '#f00', '#F90'), 6-digit hex, CSS names, \
        and Color objects.
        Args:
            bg_colors (Optional[Sequence[ColorType | Color]]): A list of background colors as \
            Color instances or strings.
            hues (int): The number of hues to generate if bgcolors are not provided.
        Returns:
            List[Color]: A list of Color objects for background colors.
        """
        if not bg_colors:
            self._interpolate_bg_colors = False
            # Default to transparent/default background per character count
            fallback = Color.parse("default")
            color_count = max(1, len(self.colors))
            return [fallback] * color_count

        if len(bg_colors) == 1:
            # If only one background color is provided, do not interpolate
            self._interpolate_bg_colors = False
            c = bg_colors[0]
            try:
                normalized = Text._normalize_color(c)
            except Exception as exc:  # pragma: no cover - defensive
                raise ColorParseError(f"Unsupported background color: {c}") from exc
            return [normalized] * max(1, len(self.colors))

        # Multiple bg_colors: interpolate across provided stops
        self._interpolate_bg_colors = True
        parsed_bg: List[Color] = []
        for c in bg_colors:
            try:
                parsed_bg.append(Text._normalize_color(c))
            except Exception as exc:  # pragma: no cover - defensive
                raise ColorParseError(f"Unsupported background color: {c}") from exc
        return parsed_bg

    def interpolate_colors(
        self, colors: Optional[Sequence[Color]] = None
    ) -> list[Color]:
        """Interpolate colors across the text using gamma-correct blending."""
        colors = list(colors) if colors is not None else self.colors
        if not colors:
            raise ValueError("No colors to interpolate")

        text = self.plain
        length = len(text)
        if length == 0:
            return []
        num_colors = len(colors)
        if num_colors == 1:
            return [colors[0]] * length

        segments = num_colors - 1
        result: List[Color] = []

        GAMMA = 2.2

        def to_linear(v: int) -> float:
            return (v / 255.0) ** GAMMA

        def to_srgb(x: float) -> int:
            return int(((x ** (1.0 / GAMMA)) * 255.0))

        for i in range(length):
            pos = i / (length - 1) if length > 1 else 0.0
            fidx = pos * segments
            idx = int(fidx)
            if idx >= segments:
                idx = segments - 1
                t = 1.0
            else:
                t = fidx - idx

            c0 = colors[idx].get_truecolor()
            c1 = colors[idx + 1].get_truecolor()

            lr = to_linear(c0.red) + (to_linear(c1.red) - to_linear(c0.red)) * t
            lg = to_linear(c0.green) + (to_linear(c1.green) - to_linear(c0.green)) * t
            lb = to_linear(c0.blue) + (to_linear(c1.blue) - to_linear(c0.blue)) * t

            r, g, b = to_srgb(lr), to_srgb(lg), to_srgb(lb)
            result.append(Color.from_rgb(r, g, b))

        return result

    def apply_gradient(self) -> None:
        """Apply interpolated colors as spans to each character in the text."""
        # Generate a color for each character
        colors = self.interpolate_colors(self.colors)
        if self._interpolate_bg_colors:
            # Generate a background color for each character if bg_colors are interpolated
            bg_colors = self.interpolate_colors(self.bg_colors)
        else:
            # If not interpolating background colors, use the first bg_color for all characters
            bg_colors = [self.bg_colors[0]] * len(colors)
        # Apply a style span for each character with its corresponding color
        for index, (color, bg_color) in enumerate(zip(colors, bg_colors)):
            # Build a style with the interpolated color
            span_style = Style(color=color, bgcolor=bg_color)
            # Stylize the single character range
            self.stylize(span_style, index, index + 1)

    def as_rich(self) -> RichText:
        """Return a plain ``rich.text.Text`` with styles and spans applied.

        This converts the current gradient-aware ``Text`` into a base
        ``rich.text.Text`` carrying the same plain content and span/style
        information. Useful when a consumer specifically needs the base type.
        """
        # Create a plain RichText that mirrors the source content and layout
        rich_text = RichText(
            self.plain,
            style=self.style,
            justify=self.justify,
            overflow=self.overflow,
            no_wrap=self.no_wrap,
            end=self.end,
            tab_size=self.tab_size,
        )

        # Copy internal spans from the source into the returned RichText.
        # Using the internal _spans attribute is acceptable here since both
        # classes share the same underlying implementation in rich.
        for span in getattr(self, "_spans", []):
            rich_text._spans.append(span)

        return rich_text

    @property
    def rich(self) -> RichText:
        """Return the underlying RichText instance."""
        return self.as_rich()

    def __rich_console__(self, console: Console, options) -> Iterable[Segment]:
        """Render Text while suppressing any output for empty content.

        For empty plain text, yield no segments at all (no stray newlines).
        Otherwise, delegate to the parent implementation and filter a final
        trailing `end` segment as required.
        """
        if self.plain == "":
            return
        for render_output in super().__rich_console__(console, options):
            if isinstance(render_output, Segment):
                # For empty Text,
                # filter out both the empty text Segment and the trailing end Segment.
                if self.plain == "" and render_output.text in ("", self.end):
                    continue
                yield render_output
            else:
                # Render nested renderable to segments, filter as needed
                for seg in console.render(render_output, options):
                    if self.plain == "" and seg.text in ("", self.end):
                        continue
                    yield seg

bg_colors property writable

Return the list of background colors in the gradient.

colors property writable

Return the list of colors in the gradient.

rich property

Return the underlying RichText instance.

__init__(text='', colors=None, hues=5, rainbow=False, style='', justify='default', overflow='fold', no_wrap=False, end='\n', tab_size=4, bg_colors=None, markup=True, spans=None)

Initialize the Text with gradient colors and styles. Args: text (TextType): The text content. colors (Optional[List[ColorType]]): A list of colors as Color instances or strings. rainbow (bool): If True, generate a rainbow spectrum. hues (int): The number of hues to generate if colors are not provided. style (StyleType): The style of the text. justify (JustifyMethod): Justification method for the text. Defaults to default. overflow (OverflowMethod): Overflow method for the text. Defaults to fold. no_wrap (bool): If True, disable wrapping of the text. Defaults to False. end (str): The string to append at the end of the text. Default is a newline (\n). tab_size (int): The number of spaces for a tab character. Defaults to 4. bg_colors (Optional[List[ColorType]]): A list of background colors as Color instances. Defaults to None. markup (bool): If True, parse Rich markup tags in the input text. Defaults to True. spans (Optional[Sequence[Span]]): A list of spans to apply to the text. Defaults to None.

Source code in src/rich_gradient/text.py
    def __init__(
        self,
        text: TextType = "",
        colors: Optional[Sequence[ColorType]] = None,
        hues: int = 5,
        rainbow: bool = False,
        style: StyleType = "",
        justify: JustifyMethod = "default",
        overflow: OverflowMethod = "fold",
        no_wrap: bool = False,
        end: str = "\n",
        tab_size: int = 4,
        bg_colors: Optional[Sequence[ColorType]] = None,
        markup: bool = True,
        spans: Optional[Sequence[Span]] = None,
    ):
        """Initialize the Text with gradient colors and styles.
        Args:
            text (TextType): The text content.
            colors (Optional[List[ColorType]]): A list of colors as Color instances or strings.
            rainbow (bool): If True, generate a rainbow spectrum.
            hues (int): The number of hues to generate if colors are not provided.
            style (StyleType): The style of the text.
            justify (JustifyMethod): Justification method for the text. Defaults to `default`.
            overflow (OverflowMethod): Overflow method for the text. Defaults to `fold`.
            no_wrap (bool): If True, disable wrapping of the text. Defaults to False.
            end (str): The string to append at the end of the text. Default is a newline (`\\n`).
            tab_size (int): The number of spaces for a tab character. Defaults to 4.
            bg_colors (Optional[List[ColorType]]): A list of background colors as Color \
instances. Defaults to None.
            markup (bool): If True, parse Rich markup tags in the input text. Defaults to True.
            spans (Optional[Sequence[Span]]): A list of spans to apply to the text. \
            Defaults to None.
        """

        # Parse the input text with or without markup
        if markup:
            parsed_text = RichText.from_markup(
                text=str(text), style=style, justify=justify, overflow=overflow
            )
        else:
            parsed_text = RichText(
                strip_control_codes(str(text)),
                style=style,
                justify=justify,
                overflow=overflow,
            )

        # Extract parsed attributes
        plain = parsed_text.plain
        parsed_justify = parsed_text.justify
        parsed_overflow = parsed_text.overflow
        parsed_spans = parsed_text._spans

        # Initialize the parent class
        super().__init__(
            plain,
            style=style,
            justify=parsed_justify,
            overflow=parsed_overflow,
            no_wrap=no_wrap,
            end=end,
            tab_size=tab_size,
            spans=parsed_spans,
        )
        self._interpolate_bg_colors = False  # Ensure flag is always initialized
        # Normalize color inputs into rich.color.Color instances
        self.colors = self.parse_colors(colors, hues, rainbow)
        self.bg_colors = self.parse_bg_colors(bg_colors, hues)

        # Handle the single-color and single-background case: apply style directly and return early
        if len(self.colors) == 1 and len(self.bg_colors) == 1:
            # Apply the single color style directly, honoring a missing style definition.
            if isinstance(style, Style):
                base_style = style
            elif isinstance(style, str) and style:
                base_style = Style.parse(style)
            else:
                base_style = Style.null()
            color_layer = Style(color=self.colors[0], bgcolor=self.bg_colors[0])
            style_with_color = color_layer + base_style
            for index in range(len(self.plain)):
                self.stylize(style_with_color, index, index + 1)
            return

        # Apply the gradient coloring
        self.apply_gradient()

__rich_console__(console, options)

Render Text while suppressing any output for empty content.

For empty plain text, yield no segments at all (no stray newlines). Otherwise, delegate to the parent implementation and filter a final trailing end segment as required.

Source code in src/rich_gradient/text.py
def __rich_console__(self, console: Console, options) -> Iterable[Segment]:
    """Render Text while suppressing any output for empty content.

    For empty plain text, yield no segments at all (no stray newlines).
    Otherwise, delegate to the parent implementation and filter a final
    trailing `end` segment as required.
    """
    if self.plain == "":
        return
    for render_output in super().__rich_console__(console, options):
        if isinstance(render_output, Segment):
            # For empty Text,
            # filter out both the empty text Segment and the trailing end Segment.
            if self.plain == "" and render_output.text in ("", self.end):
                continue
            yield render_output
        else:
            # Render nested renderable to segments, filter as needed
            for seg in console.render(render_output, options):
                if self.plain == "" and seg.text in ("", self.end):
                    continue
                yield seg

_normalize_color(value) staticmethod

Normalize a single color-like value to a rich.color.Color. Accepts: Color, ColorTriplet, 3-tuple of ints, or string parsable by Color.parse. Note that rich-color-ext expands what is considered a valid color input.

Parameters:

Name Type Description Default
value ColorType

The color-like value to normalize.

required

Returns: Color: The normalized Color instance. Raises: ColorParseError: If the color value cannot be parsed.

Source code in src/rich_gradient/text.py
@staticmethod
def _normalize_color(value: ColorType) -> Color:
    """Normalize a single color-like value to a rich.color.Color.
    Accepts: Color, ColorTriplet, 3-tuple of ints, or string parsable
    by Color.parse. Note that rich-color-ext expands what is considered
    a valid color input.

    Args:
        value (ColorType): The color-like value to normalize.
    Returns:
        Color: The normalized Color instance.
    Raises:
        ColorParseError: If the color value cannot be parsed.
    """
    try:
        if isinstance(value, Color):
            return value
        elif isinstance(value, ColorTriplet):
            return Color.from_rgb(value.red, value.green, value.blue)
        elif isinstance(value, tuple) and len(value) == 3:
            r, g, b = value
            return Color.from_rgb(int(r), int(g), int(b))
        elif isinstance(value, str):
            return Color.parse(value)
        else:
            # Reject unsupported types explicitly (e.g., int, None)
            raise TypeError(f"Unsupported color type: {type(value)!r}")
    except ColorParseError as cpe:
        raise ColorParseError(
            f"Failed to parse and normalize color: {value}"
        ) from cpe

apply_gradient()

Apply interpolated colors as spans to each character in the text.

Source code in src/rich_gradient/text.py
def apply_gradient(self) -> None:
    """Apply interpolated colors as spans to each character in the text."""
    # Generate a color for each character
    colors = self.interpolate_colors(self.colors)
    if self._interpolate_bg_colors:
        # Generate a background color for each character if bg_colors are interpolated
        bg_colors = self.interpolate_colors(self.bg_colors)
    else:
        # If not interpolating background colors, use the first bg_color for all characters
        bg_colors = [self.bg_colors[0]] * len(colors)
    # Apply a style span for each character with its corresponding color
    for index, (color, bg_color) in enumerate(zip(colors, bg_colors)):
        # Build a style with the interpolated color
        span_style = Style(color=color, bgcolor=bg_color)
        # Stylize the single character range
        self.stylize(span_style, index, index + 1)

as_rich()

Return a plain rich.text.Text with styles and spans applied.

This converts the current gradient-aware Text into a base rich.text.Text carrying the same plain content and span/style information. Useful when a consumer specifically needs the base type.

Source code in src/rich_gradient/text.py
def as_rich(self) -> RichText:
    """Return a plain ``rich.text.Text`` with styles and spans applied.

    This converts the current gradient-aware ``Text`` into a base
    ``rich.text.Text`` carrying the same plain content and span/style
    information. Useful when a consumer specifically needs the base type.
    """
    # Create a plain RichText that mirrors the source content and layout
    rich_text = RichText(
        self.plain,
        style=self.style,
        justify=self.justify,
        overflow=self.overflow,
        no_wrap=self.no_wrap,
        end=self.end,
        tab_size=self.tab_size,
    )

    # Copy internal spans from the source into the returned RichText.
    # Using the internal _spans attribute is acceptable here since both
    # classes share the same underlying implementation in rich.
    for span in getattr(self, "_spans", []):
        rich_text._spans.append(span)

    return rich_text

interpolate_colors(colors=None)

Interpolate colors across the text using gamma-correct blending.

Source code in src/rich_gradient/text.py
def interpolate_colors(
    self, colors: Optional[Sequence[Color]] = None
) -> list[Color]:
    """Interpolate colors across the text using gamma-correct blending."""
    colors = list(colors) if colors is not None else self.colors
    if not colors:
        raise ValueError("No colors to interpolate")

    text = self.plain
    length = len(text)
    if length == 0:
        return []
    num_colors = len(colors)
    if num_colors == 1:
        return [colors[0]] * length

    segments = num_colors - 1
    result: List[Color] = []

    GAMMA = 2.2

    def to_linear(v: int) -> float:
        return (v / 255.0) ** GAMMA

    def to_srgb(x: float) -> int:
        return int(((x ** (1.0 / GAMMA)) * 255.0))

    for i in range(length):
        pos = i / (length - 1) if length > 1 else 0.0
        fidx = pos * segments
        idx = int(fidx)
        if idx >= segments:
            idx = segments - 1
            t = 1.0
        else:
            t = fidx - idx

        c0 = colors[idx].get_truecolor()
        c1 = colors[idx + 1].get_truecolor()

        lr = to_linear(c0.red) + (to_linear(c1.red) - to_linear(c0.red)) * t
        lg = to_linear(c0.green) + (to_linear(c1.green) - to_linear(c0.green)) * t
        lb = to_linear(c0.blue) + (to_linear(c1.blue) - to_linear(c0.blue)) * t

        r, g, b = to_srgb(lr), to_srgb(lg), to_srgb(lb)
        result.append(Color.from_rgb(r, g, b))

    return result

parse_bg_colors(bg_colors=None, hues=5)

Parse and return a list of background colors for the gradient. Supports 3-digit hex colors (e.g., '#f00', '#F90'), 6-digit hex, CSS names, and Color objects. Args: bg_colors (Optional[Sequence[ColorType | Color]]): A list of background colors as Color instances or strings. hues (int): The number of hues to generate if bgcolors are not provided. Returns: List[Color]: A list of Color objects for background colors.

Source code in src/rich_gradient/text.py
def parse_bg_colors(
    self, bg_colors: Optional[Sequence[ColorType]] = None, hues: int = 5
) -> List[Color]:
    """Parse and return a list of background colors for the gradient.
    Supports 3-digit hex colors (e.g., '#f00', '#F90'), 6-digit hex, CSS names, \
    and Color objects.
    Args:
        bg_colors (Optional[Sequence[ColorType | Color]]): A list of background colors as \
        Color instances or strings.
        hues (int): The number of hues to generate if bgcolors are not provided.
    Returns:
        List[Color]: A list of Color objects for background colors.
    """
    if not bg_colors:
        self._interpolate_bg_colors = False
        # Default to transparent/default background per character count
        fallback = Color.parse("default")
        color_count = max(1, len(self.colors))
        return [fallback] * color_count

    if len(bg_colors) == 1:
        # If only one background color is provided, do not interpolate
        self._interpolate_bg_colors = False
        c = bg_colors[0]
        try:
            normalized = Text._normalize_color(c)
        except Exception as exc:  # pragma: no cover - defensive
            raise ColorParseError(f"Unsupported background color: {c}") from exc
        return [normalized] * max(1, len(self.colors))

    # Multiple bg_colors: interpolate across provided stops
    self._interpolate_bg_colors = True
    parsed_bg: List[Color] = []
    for c in bg_colors:
        try:
            parsed_bg.append(Text._normalize_color(c))
        except Exception as exc:  # pragma: no cover - defensive
            raise ColorParseError(f"Unsupported background color: {c}") from exc
    return parsed_bg

parse_colors(colors=None, hues=5, rainbow=False) staticmethod

Parse and return a list of colors for the gradient. Supports: - rgb colors (e.g. 'rgb(255, 0, 0)') - rgb tuples (e.g., (255, 0, 0)) - 3-digit hex colors (e.g., '#f00', '#F90') - 6-digit hex colors (e.g., '#ff0000', '#00FF00') - CSS names (e.g., 'red', 'aliceblue') - rich.color.Color objects (e.g., Color.parse('#FF0000')) Args: colors (Optional[Sequence[ColorType | Color]]): A list of colors as Color instances, tuples of integers, or strings. hues (int): The number of hues to generate if colors are not provided. Defaults to 5. rainbow (bool): Whether to generate a rainbow spectrum. Note that rainbow overrides any colors or hues provided. Defaults to False Raises: ColorParseError: If any color value cannot be parsed. ValueError: If no colors are provided, rainbow is False, and hues < 2. Returns: List[rich.color.Color]: A list of Color objects.

Source code in src/rich_gradient/text.py
    @staticmethod
    def parse_colors(
        colors: Optional[Sequence[ColorType]] = None,
        hues: int = 5,
        rainbow: bool = False,
    ) -> List[Color]:
        """Parse and return a list of colors for the gradient.
        Supports:
        - rgb colors (e.g. `'rgb(255, 0, 0)'`)
        - rgb tuples (e.g., `(255, 0, 0)`)
        - 3-digit hex colors (e.g., `'#f00'`, `'#F90'`)
        - 6-digit hex colors (e.g., `'#ff0000'`, `'#00FF00'`)
        - CSS names (e.g., `'red'`, `'aliceblue'`)
        - rich.color.Color objects (e.g., `Color.parse('#FF0000')`)
        Args:
            colors (Optional[Sequence[ColorType | Color]]): A list of colors as Color
                instances, tuples of integers, or strings.
            hues (int): The number of hues to generate if colors are not provided. Defaults to 5.
            rainbow (bool): Whether to generate a rainbow spectrum. Note that rainbow overrides
                any colors or hues provided. Defaults to False
        Raises:
            ColorParseError: If any color value cannot be parsed.
            ValueError: If no colors are provided, rainbow is False, and hues < 2.
        Returns:
            List[rich.color.Color]: A list of Color objects.
        """
        # When rainbow is True, we use a full 17-color spectrum
        if rainbow:
            return Spectrum(hues=17).colors

        # If no colors are provided, fall back to Spectrum with the specified hues
        if colors is None or len(colors) == 0:
            if hues < 2:
                raise ValueError(
                    f"If `rainbow=False` and no colors are provided, hues must be \
at least 2. Invalid hues value: {hues}"
                )
            return Spectrum(hues).colors

        # If we have colors, parse and normalize them
        parsed: List[Color] = []
        for c in colors:
            try:
                parsed.append(Text._normalize_color(c))
            except Exception as exc:  # pragma: no cover - defensive
                raise ColorParseError(f"Unsupported color value: {c}") from exc
        return parsed