#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""Python module for easy, cross-platform colored output to the terminal."""

import atexit
import itertools
import os
import platform
import sys

import colorise.formatter
from colorise.attributes import Attr  # noqa: F401

_SYSTEM_OS = platform.system().lower()

__author__ = '<NAME>'
__version__ = '1.0.1'
__license__ = 'BSD 3-Clause'
__all__ = [
    'can_redefine_colors',
    'redefine_colors',
    'color_names',
    'num_colors',
    'set_color',
    'reset_color',
    'cprint',
    'fprint',
    'highlight'
]

# Determine which platform-specific color manager to import
if _SYSTEM_OS.startswith('win'):
    from colorise.win.color_functions import (
        num_colors as _num_colors,
        redefine_colors as _redefine_colors,
        reset_color as _reset_color,
        set_color as _set_color,
    )
    from colorise.win.win32_functions import (
        can_redefine_colors as _can_redefine_colors,
        restore_console_modes,
    )

    # Ensure that the console mode set before colorise was loaded is restored
    atexit.register(restore_console_modes)
else:
    from colorise.nix.cluts import can_redefine_colors as _can_redefine_colors
    from colorise.nix.color_functions import (
        num_colors as _num_colors,
        redefine_colors as _redefine_colors,
        reset_color as _reset_color,
        set_color as _set_color,
    )


def num_colors():
    """Return the number of colors supported by the terminal."""
    return _num_colors()


def can_redefine_colors(file):
    """Return True if the terminal supports redefinition of colors.

    Only returns True for Windows 7/Vista and beyond as of now.

    """
    return _can_redefine_colors(file)


def redefine_colors(color_map, file=sys.stdout):
    """Redefine colors using a color map of indices and RGB tuples.

    .. note::

        It is not currently possible to redefine colors on Mac and Linux
        systems via colorise.

    """
    _redefine_colors(color_map, file)


def color_names():
    """Return a list of supported color names."""
    return [
        'black',
        'red',
        'green',
        'yellow',
        'blue',
        'purple',
        'magenta',
        'cyan',
        'gray',
        'grey',
        'lightgrey',
        'lightgray',
        'lightred',
        'lightgreen',
        'lightyellow',
        'lightblue',
        'lightpurple',
        'lightmagenta',
        'lightcyan',
        'white',
    ]


def set_color(fg=None, bg=None, attributes=None, file=sys.stdout):
    """Set the current colors.

    If no arguments are given, sets default colors.

    """
    if attributes is None:
        attributes = []

    _set_color(fg, bg, attributes, file)


def reset_color(file=sys.stdout):
    """Reset all colors and attributes."""
    _reset_color(file)


def cprint(
    string,
    fg=None,
    bg=None,
    attributes=None,
    end=os.linesep,
    file=sys.stdout,
    enabled=True,
):
    """Print a string to a target stream with colors and attributes.

    The fg and bg keywords specify foreground- and background colors while
    attributes is a list of desired attributes. The remaining two keyword
    arguments are the same as Python's built-in print function.

    Colors and attributes are reset before the function returns.

    """
    if attributes is None:
        attributes = []

    # Flush any remaining stuff before resetting colors
    file.flush()

    if not enabled:
        file.write(string)
        file.write(end)
        file.flush()
    else:
        reset_color(file)
        set_color(fg, bg, attributes, file)
        file.write(string)
        file.flush()  # Flush before resetting colors
        reset_color(file)

        # Make sure to print the end keyword after resetting so the next line
        # is not affected by a newline or similar
        file.write(end)
        file.flush()  # Flush before resetting colors


# Global color formatter instance
_COLOR_FORMATTER = colorise.formatter.ColorFormatter(set_color, reset_color)


def fprint(fmt, autoreset=True, end=os.linesep, file=sys.stdout, enabled=True):
    """Print a string with color formatting.

    The autoreset keyword controls if colors and attributes are reset before
    each new color format. For example:

    >>> colorise.fprint('{fg=blue}Hi {bg=red}world', autoreset=False)

    would print 'Hi' in blue foreground colors and 'world' in blue foreground
    colors AND a red background color, whereas:

    >>> colorise.fprint('{fg=blue}Hi {bg=red}world', autoreset=True)

    would print 'Hi' in blue foreground colors but 'world' only with a red
    background color since colors are reset when '{bg=red}' is encountered.

    The remaining two keyword arguments are the same as Python's built-in print
    function.

    Colors and attribtues are reset before the function returns.

    """
    _COLOR_FORMATTER.autoreset = autoreset
    _COLOR_FORMATTER.file = file
    _COLOR_FORMATTER.enabled = enabled
    _COLOR_FORMATTER.format(fmt)

    if enabled:
        file.flush()  # Flush before resetting colors
        reset_color(file)

    # Make sure to print the end keyword after resetting so the next line is
    # not affected by a newline or similar
    file.write(end)


def highlight(
    string,
    indices,
    fg=None,
    bg=None,
    attributes=None,
    end=os.linesep,
    file=sys.stdout,
    enabled=True,
):
    """Highlight characters using indices and print to a target stream.

    The indices argument is a list of indices (not necessarily sorted) for
    which to apply the colors and attributes. Indices that are out of bounds
    are ignored.

    fg and bg specify foreground- and background colors while attributes is a
    list of desired attributes. The remaining two keyword arguments are the
    same as Python's built-in print function.

    Colors and attribtues are reset before the function returns.

    """
    if attributes is None:
        attributes = []

    if not string or not indices or not (fg or bg or attributes)\
            or not enabled:
        file.write(string + end)
        return

    idx = 0
    sorted_indices = sorted(indices)

    if sorted_indices[0] < 0 or sorted_indices[-1] >= len(string):
        raise IndexError('Index out of bounds')

    # Group consecutive indices, e.g. [0, 2, 3, 5, 6] -> [(0), (2, 3), (5, 6)]
    groups = itertools.groupby(
        enumerate(sorted_indices),
        lambda x: x[0] - x[1],
    )

    # Flush any remaining stuff before resetting colors
    file.flush()
    reset_color(file)

    for _, group in groups:
        # Get the starting and ending indices of the group
        group = list(group)
        start_idx, end_idx = group[0][1], group[-1][1] + 1

        # Write anything up until the start index of the current group
        file.write(string[idx:start_idx])
        file.flush()

        set_color(fg, bg, attributes, file)

        # Write the range of characters specified by the group
        file.write(string[start_idx:end_idx])
        file.flush()

        reset_color(file)

        # Set current index to end of group
        idx = end_idx

    # Write anything that is left to write
    if idx < len(string):
        file.write(string[idx:])

    file.write(end)


def safe_atexit_reset_colors():
    """Safely reset colors."""
    # This is necessary when running the tests since pytest will redirect
    # stdout and stderr then restore them to the original values after the test
    # session. This closes stdout and stderr causing writes to the pytest
    # redirected streams to fail.
    #
    # By calling this function using the atexit module, stdout and stderr are
    # evaluated after having been restored by pytest
    #
    # See https://github.com/pytest-dev/pytest/issues/5502
    if not sys.stdout.closed:
        reset_color(sys.stdout)

    if not sys.stderr.closed:
        reset_color(sys.stderr)


# Ensure colors and attributes return to normal when colorise is quit
atexit.register(safe_atexit_reset_colors)

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""Python module for easy, cross-platform colored output to the terminal."""

import atexit
import itertools
import os
import platform
import sys

import colorise.formatter
from colorise.attributes import Attr  # noqa: F401

_SYSTEM_OS = platform.system().lower()

__author__ = '<NAME>'
__version__ = '1.0.1'
__license__ = 'BSD 3-Clause'
__all__ = [
    'can_redefine_colors',
    'redefine_colors',
    'color_names',
    'num_colors',
    'set_color',
    'reset_color',
    'cprint',
    'fprint',
    'highlight'
]

# Determine which platform-specific color manager to import
if _SYSTEM_OS.startswith('win'):
    from colorise.win.color_functions import (
        num_colors as _num_colors,
        redefine_colors as _redefine_colors,
        reset_color as _reset_color,
        set_color as _set_color,
    )
    from colorise.win.win32_functions import (
        can_redefine_colors as _can_redefine_colors,
        restore_console_modes,
    )

    # Ensure that the console mode set before colorise was loaded is restored
    atexit.register(restore_console_modes)
else:
    from colorise.nix.cluts import can_redefine_colors as _can_redefine_colors
    from colorise.nix.color_functions import (
        num_colors as _num_colors,
        redefine_colors as _redefine_colors,
        reset_color as _reset_color,
        set_color as _set_color,
    )


def num_colors():
    """Return the number of colors supported by the terminal."""
    return _num_colors()


def can_redefine_colors(file):
    """Return True if the terminal supports redefinition of colors.

    Only returns True for Windows 7/Vista and beyond as of now.

    """
    return _can_redefine_colors(file)


def redefine_colors(color_map, file=sys.stdout):
    """Redefine colors using a color map of indices and RGB tuples.

    .. note::

        It is not currently possible to redefine colors on Mac and Linux
        systems via colorise.

    """
    _redefine_colors(color_map, file)


def color_names():
    """Return a list of supported color names."""
    return [
        'black',
        'red',
        'green',
        'yellow',
        'blue',
        'purple',
        'magenta',
        'cyan',
        'gray',
        'grey',
        'lightgrey',
        'lightgray',
        'lightred',
        'lightgreen',
        'lightyellow',
        'lightblue',
        'lightpurple',
        'lightmagenta',
        'lightcyan',
        'white',
    ]


def set_color(fg=None, bg=None, attributes=None, file=sys.stdout):
    """Set the current colors.

    If no arguments are given, sets default colors.

    """
    if attributes is None:
        attributes = []

    _set_color(fg, bg, attributes, file)


def reset_color(file=sys.stdout):
    """Reset all colors and attributes."""
    _reset_color(file)


def cprint(
    string,
    fg=None,
    bg=None,
    attributes=None,
    end=os.linesep,
    file=sys.stdout,
    enabled=True,
):
    """Print a string to a target stream with colors and attributes.

    The fg and bg keywords specify foreground- and background colors while
    attributes is a list of desired attributes. The remaining two keyword
    arguments are the same as Python's built-in print function.

    Colors and attributes are reset before the function returns.

    """
    if attributes is None:
        attributes = []

    # Flush any remaining stuff before resetting colors
    file.flush()

    if not enabled:
        file.write(string)
        file.write(end)
        file.flush()
    else:
        reset_color(file)
        set_color(fg, bg, attributes, file)
        file.write(string)
        file.flush()  # Flush before resetting colors
        reset_color(file)

        # Make sure to print the end keyword after resetting so the next line
        # is not affected by a newline or similar
        file.write(end)
        file.flush()  # Flush before resetting colors


# Global color formatter instance
_COLOR_FORMATTER = colorise.formatter.ColorFormatter(set_color, reset_color)


def fprint(fmt, autoreset=True, end=os.linesep, file=sys.stdout, enabled=True):
    """Print a string with color formatting.

    The autoreset keyword controls if colors and attributes are reset before
    each new color format. For example:

    >>> colorise.fprint('{fg=blue}Hi {bg=red}world', autoreset=False)

    would print 'Hi' in blue foreground colors and 'world' in blue foreground
    colors AND a red background color, whereas:

    >>> colorise.fprint('{fg=blue}Hi {bg=red}world', autoreset=True)

    would print 'Hi' in blue foreground colors but 'world' only with a red
    background color since colors are reset when '{bg=red}' is encountered.

    The remaining two keyword arguments are the same as Python's built-in print
    function.

    Colors and attribtues are reset before the function returns.

    """
    _COLOR_FORMATTER.autoreset = autoreset
    _COLOR_FORMATTER.file = file
    _COLOR_FORMATTER.enabled = enabled
    _COLOR_FORMATTER.format(fmt)

    if enabled:
        file.flush()  # Flush before resetting colors
        reset_color(file)

    # Make sure to print the end keyword after resetting so the next line is
    # not affected by a newline or similar
    file.write(end)


def highlight(
    string,
    indices,
    fg=None,
    bg=None,
    attributes=None,
    end=os.linesep,
    file=sys.stdout,
    enabled=True,
):
    """Highlight characters using indices and print to a target stream.

    The indices argument is a list of indices (not necessarily sorted) for
    which to apply the colors and attributes. Indices that are out of bounds
    are ignored.

    fg and bg specify foreground- and background colors while attributes is a
    list of desired attributes. The remaining two keyword arguments are the
    same as Python's built-in print function.

    Colors and attribtues are reset before the function returns.

    """
    if attributes is None:
        attributes = []

    if not string or not indices or not (fg or bg or attributes)\
            or not enabled:
        file.write(string + end)
        return

    idx = 0
    sorted_indices = sorted(indices)

    if sorted_indices[0] < 0 or sorted_indices[-1] >= len(string):
        raise IndexError('Index out of bounds')

    # Group consecutive indices, e.g. [0, 2, 3, 5, 6] -> [(0), (2, 3), (5, 6)]
    groups = itertools.groupby(
        enumerate(sorted_indices),
        lambda x: x[0] - x[1],
    )

    # Flush any remaining stuff before resetting colors
    file.flush()
    reset_color(file)

    for _, group in groups:
        # Get the starting and ending indices of the group
        group = list(group)
        start_idx, end_idx = group[0][1], group[-1][1] + 1

        # Write anything up until the start index of the current group
        file.write(string[idx:start_idx])
        file.flush()

        set_color(fg, bg, attributes, file)

        # Write the range of characters specified by the group
        file.write(string[start_idx:end_idx])
        file.flush()

        reset_color(file)

        # Set current index to end of group
        idx = end_idx

    # Write anything that is left to write
    if idx < len(string):
        file.write(string[idx:])

    file.write(end)


def safe_atexit_reset_colors():
    """Safely reset colors."""
    # This is necessary when running the tests since pytest will redirect
    # stdout and stderr then restore them to the original values after the test
    # session. This closes stdout and stderr causing writes to the pytest
    # redirected streams to fail.
    #
    # By calling this function using the atexit module, stdout and stderr are
    # evaluated after having been restored by pytest
    #
    # See https://github.com/pytest-dev/pytest/issues/5502
    if not sys.stdout.closed:
        reset_color(sys.stdout)

    if not sys.stderr.closed:
        reset_color(sys.stderr)


# Ensure colors and attributes return to normal when colorise is quit
atexit.register(safe_atexit_reset_colors)

#!/usr/bin/env python # -*- coding: utf-8 -*- Python module for easy, cross-platform colored output to the terminal. # noqa: F401 # Determine which platform-specific color manager to import # Ensure that the console mode set before colorise was loaded is restored Return the number of colors supported by the terminal. Return True if the terminal supports redefinition of colors.      Only returns True for Windows 7/Vista and beyond as of now. Redefine colors using a color map of indices and RGB tuples.      .. note::          It is not currently possible to redefine colors on Mac and Linux         systems via colorise. Return a list of supported color names. Set the current colors.      If no arguments are given, sets default colors. Reset all colors and attributes. Print a string to a target stream with colors and attributes.      The fg and bg keywords specify foreground- and background colors while     attributes is a list of desired attributes. The remaining two keyword     arguments are the same as Python's built-in print function.      Colors and attributes are reset before the function returns. # Flush any remaining stuff before resetting colors # Flush before resetting colors # Make sure to print the end keyword after resetting so the next line # is not affected by a newline or similar # Flush before resetting colors # Global color formatter instance Print a string with color formatting.      The autoreset keyword controls if colors and attributes are reset before     each new color format. For example:      >>> colorise.fprint('{fg=blue}Hi {bg=red}world', autoreset=False)      would print 'Hi' in blue foreground colors and 'world' in blue foreground     colors AND a red background color, whereas:      >>> colorise.fprint('{fg=blue}Hi {bg=red}world', autoreset=True)      would print 'Hi' in blue foreground colors but 'world' only with a red     background color since colors are reset when '{bg=red}' is encountered.      The remaining two keyword arguments are the same as Python's built-in print     function.      Colors and attribtues are reset before the function returns. # Flush before resetting colors # Make sure to print the end keyword after resetting so the next line is # not affected by a newline or similar Highlight characters using indices and print to a target stream.      The indices argument is a list of indices (not necessarily sorted) for     which to apply the colors and attributes. Indices that are out of bounds     are ignored.      fg and bg specify foreground- and background colors while attributes is a     list of desired attributes. The remaining two keyword arguments are the     same as Python's built-in print function.      Colors and attribtues are reset before the function returns. # Group consecutive indices, e.g. [0, 2, 3, 5, 6] -> [(0), (2, 3), (5, 6)] # Flush any remaining stuff before resetting colors # Get the starting and ending indices of the group # Write anything up until the start index of the current group # Write the range of characters specified by the group # Set current index to end of group # Write anything that is left to write Safely reset colors. # This is necessary when running the tests since pytest will redirect # stdout and stderr then restore them to the original values after the test # session. This closes stdout and stderr causing writes to the pytest # redirected streams to fail. # # By calling this function using the atexit module, stdout and stderr are # evaluated after having been restored by pytest # # See https://github.com/pytest-dev/pytest/issues/5502 # Ensure colors and attributes return to normal when colorise is quit