from typing import Optional
from typing import Tuple


def colorize(string: str,
             rgb: Optional[Tuple[float, float, float]] = None,
             hsv: Optional[Tuple[float, float, float]] = None) -> str:
    """Add truecolor terminal color code to `string`.

    Either RGB or HSV values must be given.

    Parameters
    ----------
    string : str
        String to colorize.
    rgb : None or tuple of float
        3-tuple of RGB values in range [0, 1].
    hsv : None or tuple of float
        3-tuple of HSV values in range [0, 1].

    Returns
    -------
    str
        Input string surrounded by truecolor terminal color code.

    Examples
    --------
    >>> colorize('foo', rgb=(1, 0, 0)) # doctest: +ELLIPSIS
    '...foo...'
    >>> colorize('foo', hsv=(1, 0, 0)) # doctest: +ELLIPSIS
    '...foo...'
    """
    if hsv:
        h, s, v = hsv

        def f(n):
            k = (n + h * 6) % 6
            return v - v * s * max(min(k, 4 - k, 1), 0)

        rgb = f(5), f(3), f(1)

    if not rgb:
        raise ValueError('missing color specification')

    r, g, b = (int(255 * x) for x in rgb)
    return f'\x1b[38;2;{r};{g};{b}m{string}\x1b[0m'