Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Fixed #12112 -- Made the colors used by syntax highlighting customiza…

…ble.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@12009 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit c38d66a216a45e2352ee666eff9e1270082f906b 1 parent 9319f89
@freakboy3742 freakboy3742 authored
View
26 django/core/management/color.py
@@ -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
110 django/utils/termcolors.py
@@ -5,7 +5,6 @@
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
87 docs/ref/django-admin.txt
@@ -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
11 docs/releases/1.2.txt
@@ -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
149 tests/regressiontests/utils/termcolors.py
@@ -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
3  tests/regressiontests/utils/tests.py
@@ -9,8 +9,8 @@
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 @@
}
from dateformat import *
+from termcolors import *
class TestUtilsHtml(TestCase):
Please sign in to comment.
Something went wrong with that request. Please try again.