Commit eb82fb0a authored by Loic Bistuer's avatar Loic Bistuer
Browse files

Refactored color_style() and no_style() to improve testability. Refs #23663.

This includes the following improvements:

- The type of the style object is now called 'Style' rather than 'dummy'.
- The new make_style() function allows generating a Style object directly
  from a config string. Before the only way to get a style object was
  through the environ and it also required that the terminal supported
  colors which isn't necessarily the case when testing.
- The output of no_style() is now cached with @lru_cache.
- The output of no_style() now has the same set of attributes as the
  other Style objects. Previously it allowed anything to pass through
  with __getattr__.
parent bdb4118b
Loading
Loading
Loading
Loading
+45 −29
Original line number Diff line number Diff line
@@ -5,17 +5,18 @@ Sets up the terminal color scheme.
import os
import sys

from django.utils import lru_cache
from django.utils import termcolors


def supports_color():
    """
    Returns True if the running system's terminal supports color, and False
    otherwise.
    Returns True if the running system's terminal supports color,
    and False otherwise.
    """
    plat = sys.platform
    supported_platform = plat != 'Pocket PC' and (plat != 'win32' or
                                                  'ANSICON' in os.environ)
    supported_platform = plat != 'Pocket PC' and (plat != 'win32' or 'ANSICON' in os.environ)

    # isatty is not always implemented, #6223.
    is_a_tty = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()
    if not supported_platform or not is_a_tty:
@@ -23,34 +24,49 @@ def supports_color():
    return True


def color_style():
    """Returns a Style object with the Django color scheme."""
    if not supports_color():
        style = no_style()
    else:
        DJANGO_COLORS = os.environ.get('DJANGO_COLORS', '')
        color_settings = termcolors.parse_color_setting(DJANGO_COLORS)
        if color_settings:
            class dummy:
def make_style(config_string=''):
    """
    Create a Style object from the given config_string.

    If config_string is empty django.utils.termcolors.DEFAULT_PALETTE is used.
    """
    class Style(object):
        pass
            style = dummy()

    style = Style()

    color_settings = termcolors.parse_color_setting(config_string)

    # The nocolor palette has all available roles.
    # Use that palette as the basis for populating
    # the palette as defined in the environment.
    for role in termcolors.PALETTES[termcolors.NOCOLOR_PALETTE]:
        if color_settings:
            format = color_settings.get(role, {})
                setattr(style, role, termcolors.make_style(**format))
            style_func = termcolors.make_style(**format)
        else:
            style_func = lambda x: x
        setattr(style, role, style_func)

    # For backwards compatibility,
    # set style for ERROR_OUTPUT == ERROR
    style.ERROR_OUTPUT = style.ERROR
        else:
            style = no_style()

    return style


@lru_cache.lru_cache(maxsize=None)
def no_style():
    """Returns a Style object that has no colors."""
    class dummy:
        def __getattr__(self, attr):
            return lambda x: x
    return dummy()
    """
    Returns a Style object with no color scheme.
    """
    return make_style('nocolor')


def color_style():
    """
    Returns a Style object from the Django color scheme.
    """
    if not supports_color():
        return no_style()
    return make_style(os.environ.get('DJANGO_COLORS', ''))
+16 −0
Original line number Diff line number Diff line
@@ -1392,6 +1392,22 @@ class CommandTypes(AdminScriptTestCase):
        self.assertOutput(out, "Prints the CREATE TABLE, custom SQL and CREATE INDEX SQL statements for the\ngiven model module name(s).")
        self.assertEqual(out.count('optional arguments'), 1)

    def test_color_style(self):
        style = color.no_style()
        self.assertEqual(style.ERROR('Hello, world!'), 'Hello, world!')

        style = color.make_style('nocolor')
        self.assertEqual(style.ERROR('Hello, world!'), 'Hello, world!')

        style = color.make_style('dark')
        self.assertIn('Hello, world!', style.ERROR('Hello, world!'))
        self.assertNotEqual(style.ERROR('Hello, world!'), 'Hello, world!')

        # Default palette has color.
        style = color.make_style('')
        self.assertIn('Hello, world!', style.ERROR('Hello, world!'))
        self.assertNotEqual(style.ERROR('Hello, world!'), 'Hello, world!')

    def test_command_color(self):
        class Command(BaseCommand):
            requires_system_checks = False