Fixed #12112 -- Made the colors used by syntax highlighting customizable.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@12009 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Russell Keith-Magee 2009-12-28 06:48:47 +00:00
parent 9319f89547
commit c38d66a216
6 changed files with 368 additions and 18 deletions

View File

@ -2,6 +2,7 @@
Sets up the terminal color scheme.
"""
import os
import sys
from django.utils import termcolors
@ -21,16 +22,21 @@ def supports_color():
def color_style():
"""Returns a Style object with the Django color scheme."""
if not supports_color():
return no_style()
class dummy: pass
style = dummy()
style.ERROR = termcolors.make_style(fg='red', opts=('bold',))
style.ERROR_OUTPUT = termcolors.make_style(fg='red', opts=('bold',))
style.NOTICE = termcolors.make_style(fg='red')
style.SQL_FIELD = termcolors.make_style(fg='green', opts=('bold',))
style.SQL_COLTYPE = termcolors.make_style(fg='green')
style.SQL_KEYWORD = termcolors.make_style(fg='yellow')
style.SQL_TABLE = termcolors.make_style(opts=('bold',))
style = no_style()
else:
DJANGO_COLORS = os.environ.get('DJANGO_COLORS', '')
color_settings = termcolors.parse_color_setting(DJANGO_COLORS)
if color_settings:
class dummy: pass
style = dummy()
# The nocolor palette has all available roles.
# Use that pallete 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))
else:
style = no_style()
return style
def no_style():

View File

@ -5,7 +5,6 @@ termcolors.py
color_names = ('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white')
foreground = dict([(color_names[x], '3%s' % x) for x in range(8)])
background = dict([(color_names[x], '4%s' % x) for x in range(8)])
del color_names
RESET = '0'
opt_dict = {'bold': '1', 'underscore': '4', 'blink': '5', 'reverse': '7', 'conceal': '8'}
@ -66,3 +65,112 @@ def make_style(opts=(), **kwargs):
COMMENT = make_style(fg='blue', opts=('bold',))
"""
return lambda text: colorize(text, opts, **kwargs)
NOCOLOR_PALETTE = 'nocolor'
DARK_PALETTE = 'dark'
LIGHT_PALETTE = 'light'
PALETTES = {
NOCOLOR_PALETTE: {
'ERROR': {},
'NOTICE': {},
'SQL_FIELD': {},
'SQL_COLTYPE': {},
'SQL_KEYWORD': {},
'SQL_TABLE': {},
},
DARK_PALETTE: {
'ERROR': { 'fg': 'red', 'opts': ('bold',) },
'NOTICE': { 'fg': 'red' },
'SQL_FIELD': { 'fg': 'green', 'opts': ('bold',) },
'SQL_COLTYPE': { 'fg': 'green' },
'SQL_KEYWORD': { 'fg': 'yellow' },
'SQL_TABLE': { 'opts': ('bold',) },
},
LIGHT_PALETTE: {
'ERROR': { 'fg': 'red', 'opts': ('bold',) },
'NOTICE': { 'fg': 'red' },
'SQL_FIELD': { 'fg': 'green', 'opts': ('bold',) },
'SQL_COLTYPE': { 'fg': 'green' },
'SQL_KEYWORD': { 'fg': 'blue' },
'SQL_TABLE': { 'opts': ('bold',) },
}
}
DEFAULT_PALETTE = DARK_PALETTE
def parse_color_setting(config_string):
"""Parse a DJANGO_COLORS environment variable to produce the system palette
The general form of a pallete definition is:
"palette;role=fg;role=fg/bg;role=fg,option,option;role=fg/bg,option,option"
where:
palette is a named palette; one of 'light', 'dark', or 'nocolor'.
role is a named style used by Django
fg is a background color.
bg is a background color.
option is a display options.
Specifying a named palette is the same as manually specifying the individual
definitions for each role. Any individual definitions following the pallete
definition will augment the base palette definition.
Valid roles:
'error', 'notice', 'sql_field', 'sql_coltype', 'sql_keyword', 'sql_table'
Valid colors:
'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'
Valid options:
'bold', 'underscore', 'blink', 'reverse', 'conceal'
"""
if not config_string:
return PALETTES[DEFAULT_PALETTE]
# Split the color configuration into parts
parts = config_string.lower().split(';')
palette = PALETTES[NOCOLOR_PALETTE].copy()
for part in parts:
if part in PALETTES:
# A default palette has been specified
palette.update(PALETTES[part])
elif '=' in part:
# Process a palette defining string
definition = {}
# Break the definition into the role,
# plus the list of specific instructions.
# The role must be in upper case
role, instructions = part.split('=')
role = role.upper()
styles = instructions.split(',')
styles.reverse()
# The first instruction can contain a slash
# to break apart fg/bg.
colors = styles.pop().split('/')
colors.reverse()
fg = colors.pop()
if fg in color_names:
definition['fg'] = fg
if colors and colors[-1] in color_names:
definition['bg'] = colors[-1]
# All remaining instructions are options
opts = tuple(s for s in styles if s in opt_dict.keys())
if opts:
definition['opts'] = opts
# The nocolor palette has all available roles.
# Use that palette as the basis for determining
# if the role is valid.
if role in PALETTES[NOCOLOR_PALETTE] and definition:
palette[role] = definition
# If there are no colors specified, return the empty palette.
if palette == PALETTES[NOCOLOR_PALETTE]:
return None
return palette

View File

@ -991,13 +991,92 @@ being executed as an unattended, automated script.
Extra niceties
==============
.. _syntax-coloring:
Syntax coloring
---------------
The ``django-admin.py`` / ``manage.py`` commands that output SQL to standard
output will use pretty color-coded output if your terminal supports
ANSI-colored output. It won't use the color codes if you're piping the
command's output to another program.
The ``django-admin.py`` / ``manage.py`` commands that output SQL to
standard output will use pretty color-coded output if your terminal
supports ANSI-colored output. It won't use the color codes if you're
piping the command's output to another program.
The colors used for syntax highlighting can be customized. Django
ships with three color palettes:
* ``dark``, suited to terminals that show white text on a black
background. This is the default palette.
* ``light``, suited to terminals that show white text on a black
background.
* ``nocolor``, which disables syntax highlighting.
You select a palette by setting a ``DJANGO_COLORS`` environment
variable to specify the palette you want to use. For example, to
specify the ``light`` palette under a Unix or OS/X BASH shell, you
would run the following at a command prompt::
export DJANGO_COLORS="light"
You can also customize the colors that are used. Django specifies a
number of roles in which color is used:
* ``error`` - A major error.
* ``notice`` - A minor error.
* ``sql_field`` - The name of a model field in SQL.
* ``sql_coltype`` - The type of a model field in SQL.
* ``sql_keyword`` - A SQL keyword.
* ``sql_table`` - The name of a model in SQL.
Each of these roles can be assigned a specific foreground and
background color, from the following list:
* ``black``
* ``red``
* ``green``
* ``yellow``
* ``blue``
* ``magenta``
* ``cyan``
* ``white``
Each of these colors can then be modified by using the following
display options:
* ``bold``
* ``underscore``
* ``blink``
* ``reverse``
* ``conceal``
A color specification follows one of the the following patterns:
* ``role=fg``
* ``role=fg/bg``
* ``role=fg,option,option``
* ``role=fg/bg,option,option``
where ``role`` is the name of a valid color role, ``fg`` is the
foreground color, ``bg`` is the background color and each ``option``
is one of the color modifying options. Multiple color specifications
are then separated by semicolon. For example::
export DJANGO_COLORS="error=yellow/blue,blink;notice=magenta"
would specify that errors be displayed using blinking yellow on blue,
and notices displayed using magenta. All other color roles would be
left uncolored.
Colors can also be specified by extending a base palette. If you put
a palette name in a color specification, all the colors implied by that
palette will be loaded. So::
export DJANGO_COLORS="light;error=yellow/blue,blink;notice=magenta"
would specify the use of all the colors in the light color palette,
*except* for the colors for errors and notices which would be
overridden as specified.
Bash completion
---------------

View File

@ -472,8 +472,8 @@ Fast Failure for Tests
----------------------
The ``test`` subcommand of ``django-admin.py``, and the ``runtests.py`` script
used to run Django's own test suite, support a new ``--failfast`` option.
When specified, this option causes the test runner to exit after
used to run Django's own test suite, support a new ``--failfast`` option.
When specified, this option causes the test runner to exit after
encountering a failure instead of continuing with the test run.
Improved localization
@ -492,3 +492,10 @@ Added ``readonly_fields`` to ``ModelAdmin``
:attr:`django.contrib.admin.ModelAdmin.readonly_fields` has been added to
enable non-editable fields in add/change pages for models and inlines. Field
and calculated values can be displayed along side editable fields.
Customizable syntax highlighting
--------------------------------
You can now use the ``DJANGO_COLORS`` environment variable to modify
or disable the colors used by ``django-admin.py`` to provide
:ref:`syntax highlighting <syntax-coloring>`.

View File

@ -0,0 +1,149 @@
from unittest import TestCase
from django.utils.termcolors import parse_color_setting, PALETTES, DEFAULT_PALETTE, LIGHT_PALETTE, DARK_PALETTE, NOCOLOR_PALETTE
class TermColorTests(TestCase):
def test_empty_string(self):
self.assertEquals(parse_color_setting(''), PALETTES[DEFAULT_PALETTE])
def test_simple_palette(self):
self.assertEquals(parse_color_setting('light'), PALETTES[LIGHT_PALETTE])
self.assertEquals(parse_color_setting('dark'), PALETTES[DARK_PALETTE])
self.assertEquals(parse_color_setting('nocolor'), None)
def test_fg(self):
self.assertEquals(parse_color_setting('error=green'),
dict(PALETTES[NOCOLOR_PALETTE],
ERROR={'fg':'green'}))
def test_fg_bg(self):
self.assertEquals(parse_color_setting('error=green/blue'),
dict(PALETTES[NOCOLOR_PALETTE],
ERROR={'fg':'green', 'bg':'blue'}))
def test_fg_opts(self):
self.assertEquals(parse_color_setting('error=green,blink'),
dict(PALETTES[NOCOLOR_PALETTE],
ERROR={'fg':'green', 'opts': ('blink',)}))
self.assertEquals(parse_color_setting('error=green,bold,blink'),
dict(PALETTES[NOCOLOR_PALETTE],
ERROR={'fg':'green', 'opts': ('blink','bold')}))
def test_fg_bg_opts(self):
self.assertEquals(parse_color_setting('error=green/blue,blink'),
dict(PALETTES[NOCOLOR_PALETTE],
ERROR={'fg':'green', 'bg':'blue', 'opts': ('blink',)}))
self.assertEquals(parse_color_setting('error=green/blue,bold,blink'),
dict(PALETTES[NOCOLOR_PALETTE],
ERROR={'fg':'green', 'bg':'blue', 'opts': ('blink','bold')}))
def test_override_palette(self):
self.assertEquals(parse_color_setting('light;error=green'),
dict(PALETTES[LIGHT_PALETTE],
ERROR={'fg':'green'}))
def test_override_nocolor(self):
self.assertEquals(parse_color_setting('nocolor;error=green'),
dict(PALETTES[NOCOLOR_PALETTE],
ERROR={'fg': 'green'}))
def test_reverse_override(self):
self.assertEquals(parse_color_setting('error=green;light'), PALETTES[LIGHT_PALETTE])
def test_multiple_roles(self):
self.assertEquals(parse_color_setting('error=green;sql_field=blue'),
dict(PALETTES[NOCOLOR_PALETTE],
ERROR={'fg':'green'},
SQL_FIELD={'fg':'blue'}))
def test_override_with_multiple_roles(self):
self.assertEquals(parse_color_setting('light;error=green;sql_field=blue'),
dict(PALETTES[LIGHT_PALETTE],
ERROR={'fg':'green'},
SQL_FIELD={'fg':'blue'}))
def test_empty_definition(self):
self.assertEquals(parse_color_setting(';'), None)
self.assertEquals(parse_color_setting('light;'), PALETTES[LIGHT_PALETTE])
self.assertEquals(parse_color_setting(';;;'), None)
def test_empty_options(self):
self.assertEquals(parse_color_setting('error=green,'),
dict(PALETTES[NOCOLOR_PALETTE],
ERROR={'fg':'green'}))
self.assertEquals(parse_color_setting('error=green,,,'),
dict(PALETTES[NOCOLOR_PALETTE],
ERROR={'fg':'green'}))
self.assertEquals(parse_color_setting('error=green,,blink,,'),
dict(PALETTES[NOCOLOR_PALETTE],
ERROR={'fg':'green', 'opts': ('blink',)}))
def test_bad_palette(self):
self.assertEquals(parse_color_setting('unknown'), None)
def test_bad_role(self):
self.assertEquals(parse_color_setting('unknown='), None)
self.assertEquals(parse_color_setting('unknown=green'), None)
self.assertEquals(parse_color_setting('unknown=green;sql_field=blue'),
dict(PALETTES[NOCOLOR_PALETTE],
SQL_FIELD={'fg':'blue'}))
def test_bad_color(self):
self.assertEquals(parse_color_setting('error='), None)
self.assertEquals(parse_color_setting('error=;sql_field=blue'),
dict(PALETTES[NOCOLOR_PALETTE],
SQL_FIELD={'fg':'blue'}))
self.assertEquals(parse_color_setting('error=unknown'), None)
self.assertEquals(parse_color_setting('error=unknown;sql_field=blue'),
dict(PALETTES[NOCOLOR_PALETTE],
SQL_FIELD={'fg':'blue'}))
self.assertEquals(parse_color_setting('error=green/unknown'),
dict(PALETTES[NOCOLOR_PALETTE],
ERROR={'fg':'green'}))
self.assertEquals(parse_color_setting('error=green/blue/something'),
dict(PALETTES[NOCOLOR_PALETTE],
ERROR={'fg':'green', 'bg': 'blue'}))
self.assertEquals(parse_color_setting('error=green/blue/something,blink'),
dict(PALETTES[NOCOLOR_PALETTE],
ERROR={'fg':'green', 'bg': 'blue', 'opts': ('blink',)}))
def test_bad_option(self):
self.assertEquals(parse_color_setting('error=green,unknown'),
dict(PALETTES[NOCOLOR_PALETTE],
ERROR={'fg':'green'}))
self.assertEquals(parse_color_setting('error=green,unknown,blink'),
dict(PALETTES[NOCOLOR_PALETTE],
ERROR={'fg':'green', 'opts': ('blink',)}))
def test_role_case(self):
self.assertEquals(parse_color_setting('ERROR=green'),
dict(PALETTES[NOCOLOR_PALETTE],
ERROR={'fg':'green'}))
self.assertEquals(parse_color_setting('eRrOr=green'),
dict(PALETTES[NOCOLOR_PALETTE],
ERROR={'fg':'green'}))
def test_color_case(self):
self.assertEquals(parse_color_setting('error=GREEN'),
dict(PALETTES[NOCOLOR_PALETTE],
ERROR={'fg':'green'}))
self.assertEquals(parse_color_setting('error=GREEN/BLUE'),
dict(PALETTES[NOCOLOR_PALETTE],
ERROR={'fg':'green', 'bg':'blue'}))
self.assertEquals(parse_color_setting('error=gReEn'),
dict(PALETTES[NOCOLOR_PALETTE],
ERROR={'fg':'green'}))
self.assertEquals(parse_color_setting('error=gReEn/bLuE'),
dict(PALETTES[NOCOLOR_PALETTE],
ERROR={'fg':'green', 'bg':'blue'}))
def test_opts_case(self):
self.assertEquals(parse_color_setting('error=green,BLINK'),
dict(PALETTES[NOCOLOR_PALETTE],
ERROR={'fg':'green', 'opts': ('blink',)}))
self.assertEquals(parse_color_setting('error=green,bLiNk'),
dict(PALETTES[NOCOLOR_PALETTE],
ERROR={'fg':'green', 'opts': ('blink',)}))

View File

@ -9,8 +9,8 @@ from django.utils.functional import SimpleLazyObject
import timesince
import datastructures
import dateformat
import itercompat
from decorators import DecoratorFromMiddlewareTests
# We need this because "datastructures" uses sorted() and the tests are run in
@ -28,6 +28,7 @@ __test__ = {
}
from dateformat import *
from termcolors import *
class TestUtilsHtml(TestCase):