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__.
This commit is contained in:
Loic Bistuer 2014-10-21 15:31:24 +07:00
parent bdb4118b1a
commit eb82fb0a9d
2 changed files with 61 additions and 29 deletions

View File

@ -5,17 +5,18 @@ Sets up the terminal color scheme.
import os import os
import sys import sys
from django.utils import lru_cache
from django.utils import termcolors from django.utils import termcolors
def supports_color(): def supports_color():
""" """
Returns True if the running system's terminal supports color, and False Returns True if the running system's terminal supports color,
otherwise. and False otherwise.
""" """
plat = sys.platform plat = sys.platform
supported_platform = plat != 'Pocket PC' and (plat != 'win32' or supported_platform = plat != 'Pocket PC' and (plat != 'win32' or 'ANSICON' in os.environ)
'ANSICON' in os.environ)
# isatty is not always implemented, #6223. # isatty is not always implemented, #6223.
is_a_tty = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty() is_a_tty = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()
if not supported_platform or not is_a_tty: if not supported_platform or not is_a_tty:
@ -23,34 +24,49 @@ def supports_color():
return True return True
def color_style(): def make_style(config_string=''):
"""Returns a Style object with the Django color scheme.""" """
if not supports_color(): Create a Style object from the given config_string.
style = no_style()
else: If config_string is empty django.utils.termcolors.DEFAULT_PALETTE is used.
DJANGO_COLORS = os.environ.get('DJANGO_COLORS', '') """
color_settings = termcolors.parse_color_setting(DJANGO_COLORS) class Style(object):
if color_settings:
class dummy:
pass pass
style = dummy()
style = Style()
color_settings = termcolors.parse_color_setting(config_string)
# The nocolor palette has all available roles. # The nocolor palette has all available roles.
# Use that palette as the basis for populating # Use that palette as the basis for populating
# the palette as defined in the environment. # the palette as defined in the environment.
for role in termcolors.PALETTES[termcolors.NOCOLOR_PALETTE]: for role in termcolors.PALETTES[termcolors.NOCOLOR_PALETTE]:
if color_settings:
format = color_settings.get(role, {}) 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, # For backwards compatibility,
# set style for ERROR_OUTPUT == ERROR # set style for ERROR_OUTPUT == ERROR
style.ERROR_OUTPUT = style.ERROR style.ERROR_OUTPUT = style.ERROR
else:
style = no_style()
return style return style
@lru_cache.lru_cache(maxsize=None)
def no_style(): def no_style():
"""Returns a Style object that has no colors.""" """
class dummy: Returns a Style object with no color scheme.
def __getattr__(self, attr): """
return lambda x: x return make_style('nocolor')
return dummy()
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', ''))

View File

@ -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.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) 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): def test_command_color(self):
class Command(BaseCommand): class Command(BaseCommand):
requires_system_checks = False requires_system_checks = False