From eb82fb0a9d14ca317d366afb65e21d742bbe46a5 Mon Sep 17 00:00:00 2001 From: Loic Bistuer Date: Tue, 21 Oct 2014 15:31:24 +0700 Subject: [PATCH] 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__. --- django/core/management/color.py | 74 ++++++++++++++++++++------------- tests/admin_scripts/tests.py | 16 +++++++ 2 files changed, 61 insertions(+), 29 deletions(-) diff --git a/django/core/management/color.py b/django/core/management/color.py index 3890a4546f..ce47f318ff 100644 --- a/django/core/management/color.py +++ b/django/core/management/color.py @@ -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) +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 = 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: - class dummy: - pass - style = dummy() - # 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]: - format = color_settings.get(role, {}) - setattr(style, role, termcolors.make_style(**format)) - # For backwards compatibility, - # set style for ERROR_OUTPUT == ERROR - style.ERROR_OUTPUT = style.ERROR + format = color_settings.get(role, {}) + style_func = termcolors.make_style(**format) else: - style = no_style() + style_func = lambda x: x + setattr(style, role, style_func) + + # For backwards compatibility, + # set style for ERROR_OUTPUT == ERROR + style.ERROR_OUTPUT = style.ERROR + 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', '')) diff --git a/tests/admin_scripts/tests.py b/tests/admin_scripts/tests.py index c19fbb9019..5bc0fb3ee1 100644 --- a/tests/admin_scripts/tests.py +++ b/tests/admin_scripts/tests.py @@ -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