Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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
commit c38d66a216a45e2352ee666eff9e1270082f906b 1 parent 9319f89
Russell Keith-Magee authored December 28, 2009
26  django/core/management/color.py
@@ -2,6 +2,7 @@
2 2
 Sets up the terminal color scheme.
3 3
 """
4 4
 
  5
+import os
5 6
 import sys
6 7
 
7 8
 from django.utils import termcolors
@@ -21,16 +22,21 @@ def supports_color():
21 22
 def color_style():
22 23
     """Returns a Style object with the Django color scheme."""
23 24
     if not supports_color():
24  
-        return no_style()
25  
-    class dummy: pass
26  
-    style = dummy()
27  
-    style.ERROR = termcolors.make_style(fg='red', opts=('bold',))
28  
-    style.ERROR_OUTPUT = termcolors.make_style(fg='red', opts=('bold',))
29  
-    style.NOTICE = termcolors.make_style(fg='red')
30  
-    style.SQL_FIELD = termcolors.make_style(fg='green', opts=('bold',))
31  
-    style.SQL_COLTYPE = termcolors.make_style(fg='green')
32  
-    style.SQL_KEYWORD = termcolors.make_style(fg='yellow')
33  
-    style.SQL_TABLE = termcolors.make_style(opts=('bold',))
  25
+        style = no_style()
  26
+    else:
  27
+        DJANGO_COLORS = os.environ.get('DJANGO_COLORS', '')
  28
+        color_settings = termcolors.parse_color_setting(DJANGO_COLORS)
  29
+        if color_settings:
  30
+            class dummy: pass
  31
+            style = dummy()
  32
+            # The nocolor palette has all available roles.
  33
+            # Use that pallete as the basis for populating
  34
+            # the palette as defined in the environment.
  35
+            for role in termcolors.PALETTES[termcolors.NOCOLOR_PALETTE]:
  36
+                format = color_settings.get(role,{})
  37
+                setattr(style, role, termcolors.make_style(**format))
  38
+        else:
  39
+            style = no_style()
34 40
     return style
35 41
 
36 42
 def no_style():
110  django/utils/termcolors.py
@@ -5,7 +5,6 @@
5 5
 color_names = ('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white')
6 6
 foreground = dict([(color_names[x], '3%s' % x) for x in range(8)])
7 7
 background = dict([(color_names[x], '4%s' % x) for x in range(8)])
8  
-del color_names
9 8
 
10 9
 RESET = '0'
11 10
 opt_dict = {'bold': '1', 'underscore': '4', 'blink': '5', 'reverse': '7', 'conceal': '8'}
@@ -66,3 +65,112 @@ def make_style(opts=(), **kwargs):
66 65
         COMMENT = make_style(fg='blue', opts=('bold',))
67 66
     """
68 67
     return lambda text: colorize(text, opts, **kwargs)
  68
+
  69
+NOCOLOR_PALETTE = 'nocolor'
  70
+DARK_PALETTE = 'dark'
  71
+LIGHT_PALETTE = 'light'
  72
+
  73
+PALETTES = {
  74
+    NOCOLOR_PALETTE: {
  75
+        'ERROR':        {},
  76
+        'NOTICE':       {},
  77
+        'SQL_FIELD':    {},
  78
+        'SQL_COLTYPE':  {},
  79
+        'SQL_KEYWORD':  {},
  80
+        'SQL_TABLE':    {},
  81
+    },
  82
+    DARK_PALETTE: {
  83
+        'ERROR':        { 'fg': 'red', 'opts': ('bold',) },
  84
+        'NOTICE':       { 'fg': 'red' },
  85
+        'SQL_FIELD':    { 'fg': 'green', 'opts': ('bold',) },
  86
+        'SQL_COLTYPE':  { 'fg': 'green' },
  87
+        'SQL_KEYWORD':  { 'fg': 'yellow' },
  88
+        'SQL_TABLE':    { 'opts': ('bold',) },
  89
+    },
  90
+    LIGHT_PALETTE: {
  91
+        'ERROR':        { 'fg': 'red', 'opts': ('bold',) },
  92
+        'NOTICE':       { 'fg': 'red' },
  93
+        'SQL_FIELD':    { 'fg': 'green', 'opts': ('bold',) },
  94
+        'SQL_COLTYPE':  { 'fg': 'green' },
  95
+        'SQL_KEYWORD':  { 'fg': 'blue' },
  96
+        'SQL_TABLE':    { 'opts': ('bold',) },
  97
+    }
  98
+}
  99
+DEFAULT_PALETTE = DARK_PALETTE
  100
+
  101
+def parse_color_setting(config_string):
  102
+    """Parse a DJANGO_COLORS environment variable to produce the system palette
  103
+
  104
+    The general form of a pallete definition is:
  105
+
  106
+        "palette;role=fg;role=fg/bg;role=fg,option,option;role=fg/bg,option,option"
  107
+
  108
+    where:
  109
+        palette is a named palette; one of 'light', 'dark', or 'nocolor'.
  110
+        role is a named style used by Django
  111
+        fg is a background color.
  112
+        bg is a background color.
  113
+        option is a display options.
  114
+
  115
+    Specifying a named palette is the same as manually specifying the individual
  116
+    definitions for each role. Any individual definitions following the pallete
  117
+    definition will augment the base palette definition.
  118
+
  119
+    Valid roles:
  120
+        'error', 'notice', 'sql_field', 'sql_coltype', 'sql_keyword', 'sql_table'
  121
+
  122
+    Valid colors:
  123
+        'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'
  124
+
  125
+    Valid options:
  126
+        'bold', 'underscore', 'blink', 'reverse', 'conceal'
  127
+
  128
+    """
  129
+    if not config_string:
  130
+        return PALETTES[DEFAULT_PALETTE]
  131
+
  132
+    # Split the color configuration into parts
  133
+    parts = config_string.lower().split(';')
  134
+    palette = PALETTES[NOCOLOR_PALETTE].copy()
  135
+    for part in parts:
  136
+        if part in PALETTES:
  137
+            # A default palette has been specified
  138
+            palette.update(PALETTES[part])
  139
+        elif '=' in part:
  140
+            # Process a palette defining string
  141
+            definition = {}
  142
+
  143
+            # Break the definition into the role,
  144
+            # plus the list of specific instructions.
  145
+            # The role must be in upper case
  146
+            role, instructions = part.split('=')
  147
+            role = role.upper()
  148
+
  149
+            styles = instructions.split(',')
  150
+            styles.reverse()
  151
+
  152
+            # The first instruction can contain a slash
  153
+            # to break apart fg/bg.
  154
+            colors = styles.pop().split('/')
  155
+            colors.reverse()
  156
+            fg = colors.pop()
  157
+            if fg in color_names:
  158
+                definition['fg'] = fg
  159
+            if colors and colors[-1] in color_names:
  160
+                definition['bg'] = colors[-1]
  161
+
  162
+            # All remaining instructions are options
  163
+            opts = tuple(s for s in styles if s in opt_dict.keys())
  164
+            if opts:
  165
+                definition['opts'] = opts
  166
+
  167
+            # The nocolor palette has all available roles.
  168
+            # Use that palette as the basis for determining
  169
+            # if the role is valid.
  170
+            if role in PALETTES[NOCOLOR_PALETTE] and definition:
  171
+                palette[role] = definition
  172
+
  173
+    # If there are no colors specified, return the empty palette.
  174
+    if palette == PALETTES[NOCOLOR_PALETTE]:
  175
+        return None
  176
+    return palette
87  docs/ref/django-admin.txt
@@ -991,13 +991,92 @@ being executed as an unattended, automated script.
991 991
 Extra niceties
992 992
 ==============
993 993
 
  994
+.. _syntax-coloring:
  995
+
994 996
 Syntax coloring
995 997
 ---------------
996 998
 
997  
-The ``django-admin.py`` / ``manage.py`` commands that output SQL to standard
998  
-output will use pretty color-coded output if your terminal supports
999  
-ANSI-colored output. It won't use the color codes if you're piping the
1000  
-command's output to another program.
  999
+The ``django-admin.py`` / ``manage.py`` commands that output SQL to
  1000
+standard output will use pretty color-coded output if your terminal
  1001
+supports ANSI-colored output. It won't use the color codes if you're
  1002
+piping the command's output to another program.
  1003
+
  1004
+The colors used for syntax highlighting can be customized. Django
  1005
+ships with three color palettes:
  1006
+
  1007
+    * ``dark``, suited to terminals that show white text on a black
  1008
+      background. This is the default palette.
  1009
+
  1010
+    * ``light``, suited to terminals that show white text on a black
  1011
+      background.
  1012
+
  1013
+    * ``nocolor``, which disables syntax highlighting.
  1014
+
  1015
+You select a palette by setting a ``DJANGO_COLORS`` environment
  1016
+variable to specify the palette you want to use. For example, to
  1017
+specify the ``light`` palette under a Unix or OS/X BASH shell, you
  1018
+would run the following at a command prompt::
  1019
+
  1020
+    export DJANGO_COLORS="light"
  1021
+
  1022
+You can also customize the colors that are used. Django specifies a
  1023
+number of roles in which color is used:
  1024
+
  1025
+    * ``error`` - A major error.
  1026
+    * ``notice`` - A minor error.
  1027
+    * ``sql_field`` - The name of a model field in SQL.
  1028
+    * ``sql_coltype`` - The type of a model field in SQL.
  1029
+    * ``sql_keyword`` - A SQL keyword.
  1030
+    * ``sql_table`` - The name of a model in SQL.
  1031
+
  1032
+Each of these roles can be assigned a specific foreground and
  1033
+background color, from the following list:
  1034
+
  1035
+    * ``black``
  1036
+    * ``red``
  1037
+    * ``green``
  1038
+    * ``yellow``
  1039
+    * ``blue``
  1040
+    * ``magenta``
  1041
+    * ``cyan``
  1042
+    * ``white``
  1043
+
  1044
+Each of these colors can then be modified by using the following
  1045
+display options:
  1046
+
  1047
+    * ``bold``
  1048
+    * ``underscore``
  1049
+    * ``blink``
  1050
+    * ``reverse``
  1051
+    * ``conceal``
  1052
+
  1053
+A color specification follows one of the the following patterns:
  1054
+
  1055
+    * ``role=fg``
  1056
+    * ``role=fg/bg``
  1057
+    * ``role=fg,option,option``
  1058
+    * ``role=fg/bg,option,option``
  1059
+
  1060
+where ``role`` is the name of a valid color role, ``fg`` is the
  1061
+foreground color, ``bg`` is the background color and each ``option``
  1062
+is one of the color modifying options. Multiple color specifications
  1063
+are then separated by semicolon. For example::
  1064
+
  1065
+    export DJANGO_COLORS="error=yellow/blue,blink;notice=magenta"
  1066
+
  1067
+would specify that errors be displayed using blinking yellow on blue,
  1068
+and notices displayed using magenta. All other color roles would be
  1069
+left uncolored.
  1070
+
  1071
+Colors can also be specified by extending a base palette. If you put
  1072
+a palette name in a color specification, all the colors implied by that
  1073
+palette will be loaded. So::
  1074
+
  1075
+    export DJANGO_COLORS="light;error=yellow/blue,blink;notice=magenta"
  1076
+
  1077
+would specify the use of all the colors in the light color palette,
  1078
+*except* for the colors for errors and notices which would be
  1079
+overridden as specified.
1001 1080
 
1002 1081
 Bash completion
1003 1082
 ---------------
11  docs/releases/1.2.txt
@@ -472,8 +472,8 @@ Fast Failure for Tests
472 472
 ----------------------
473 473
 
474 474
 The ``test`` subcommand of ``django-admin.py``, and the ``runtests.py`` script
475  
-used to run Django's own test suite, support a new ``--failfast`` option. 
476  
-When specified, this option causes the test runner to exit after 
  475
+used to run Django's own test suite, support a new ``--failfast`` option.
  476
+When specified, this option causes the test runner to exit after
477 477
 encountering a failure instead of continuing with the test run.
478 478
 
479 479
 Improved localization
@@ -492,3 +492,10 @@ Added ``readonly_fields`` to ``ModelAdmin``
492 492
 :attr:`django.contrib.admin.ModelAdmin.readonly_fields` has been added to
493 493
 enable non-editable fields in add/change pages for models and inlines. Field
494 494
 and calculated values can be displayed along side editable fields.
  495
+
  496
+Customizable syntax highlighting
  497
+--------------------------------
  498
+
  499
+You can now use the ``DJANGO_COLORS`` environment variable to modify
  500
+or disable the colors used by ``django-admin.py`` to provide
  501
+:ref:`syntax highlighting <syntax-coloring>`.
149  tests/regressiontests/utils/termcolors.py
... ...
@@ -0,0 +1,149 @@
  1
+from unittest import TestCase
  2
+
  3
+from django.utils.termcolors import parse_color_setting, PALETTES, DEFAULT_PALETTE, LIGHT_PALETTE, DARK_PALETTE, NOCOLOR_PALETTE
  4
+
  5
+class TermColorTests(TestCase):
  6
+
  7
+    def test_empty_string(self):
  8
+        self.assertEquals(parse_color_setting(''), PALETTES[DEFAULT_PALETTE])
  9
+
  10
+    def test_simple_palette(self):
  11
+        self.assertEquals(parse_color_setting('light'), PALETTES[LIGHT_PALETTE])
  12
+        self.assertEquals(parse_color_setting('dark'), PALETTES[DARK_PALETTE])
  13
+        self.assertEquals(parse_color_setting('nocolor'), None)
  14
+
  15
+    def test_fg(self):
  16
+        self.assertEquals(parse_color_setting('error=green'),
  17
+                          dict(PALETTES[NOCOLOR_PALETTE],
  18
+                            ERROR={'fg':'green'}))
  19
+
  20
+    def test_fg_bg(self):
  21
+        self.assertEquals(parse_color_setting('error=green/blue'),
  22
+                          dict(PALETTES[NOCOLOR_PALETTE],
  23
+                            ERROR={'fg':'green', 'bg':'blue'}))
  24
+
  25
+    def test_fg_opts(self):
  26
+        self.assertEquals(parse_color_setting('error=green,blink'),
  27
+                          dict(PALETTES[NOCOLOR_PALETTE],
  28
+                            ERROR={'fg':'green', 'opts': ('blink',)}))
  29
+        self.assertEquals(parse_color_setting('error=green,bold,blink'),
  30
+                          dict(PALETTES[NOCOLOR_PALETTE],
  31
+                            ERROR={'fg':'green', 'opts': ('blink','bold')}))
  32
+
  33
+    def test_fg_bg_opts(self):
  34
+        self.assertEquals(parse_color_setting('error=green/blue,blink'),
  35
+                          dict(PALETTES[NOCOLOR_PALETTE],
  36
+                            ERROR={'fg':'green', 'bg':'blue', 'opts': ('blink',)}))
  37
+        self.assertEquals(parse_color_setting('error=green/blue,bold,blink'),
  38
+                          dict(PALETTES[NOCOLOR_PALETTE],
  39
+                            ERROR={'fg':'green', 'bg':'blue', 'opts': ('blink','bold')}))
  40
+
  41
+    def test_override_palette(self):
  42
+        self.assertEquals(parse_color_setting('light;error=green'),
  43
+                          dict(PALETTES[LIGHT_PALETTE],
  44
+                            ERROR={'fg':'green'}))
  45
+
  46
+    def test_override_nocolor(self):
  47
+        self.assertEquals(parse_color_setting('nocolor;error=green'),
  48
+                          dict(PALETTES[NOCOLOR_PALETTE],
  49
+                            ERROR={'fg': 'green'}))
  50
+
  51
+    def test_reverse_override(self):
  52
+        self.assertEquals(parse_color_setting('error=green;light'), PALETTES[LIGHT_PALETTE])
  53
+
  54
+    def test_multiple_roles(self):
  55
+        self.assertEquals(parse_color_setting('error=green;sql_field=blue'),
  56
+                          dict(PALETTES[NOCOLOR_PALETTE],
  57
+                            ERROR={'fg':'green'},
  58
+                            SQL_FIELD={'fg':'blue'}))
  59
+
  60
+    def test_override_with_multiple_roles(self):
  61
+        self.assertEquals(parse_color_setting('light;error=green;sql_field=blue'),
  62
+                          dict(PALETTES[LIGHT_PALETTE],
  63
+                            ERROR={'fg':'green'},
  64
+                            SQL_FIELD={'fg':'blue'}))
  65
+
  66
+    def test_empty_definition(self):
  67
+        self.assertEquals(parse_color_setting(';'), None)
  68
+        self.assertEquals(parse_color_setting('light;'), PALETTES[LIGHT_PALETTE])
  69
+        self.assertEquals(parse_color_setting(';;;'), None)
  70
+
  71
+    def test_empty_options(self):
  72
+        self.assertEquals(parse_color_setting('error=green,'),
  73
+                          dict(PALETTES[NOCOLOR_PALETTE],
  74
+                            ERROR={'fg':'green'}))
  75
+        self.assertEquals(parse_color_setting('error=green,,,'),
  76
+                          dict(PALETTES[NOCOLOR_PALETTE],
  77
+                            ERROR={'fg':'green'}))
  78
+        self.assertEquals(parse_color_setting('error=green,,blink,,'),
  79
+                          dict(PALETTES[NOCOLOR_PALETTE],
  80
+                            ERROR={'fg':'green', 'opts': ('blink',)}))
  81
+
  82
+    def test_bad_palette(self):
  83
+        self.assertEquals(parse_color_setting('unknown'), None)
  84
+
  85
+    def test_bad_role(self):
  86
+        self.assertEquals(parse_color_setting('unknown='), None)
  87
+        self.assertEquals(parse_color_setting('unknown=green'), None)
  88
+        self.assertEquals(parse_color_setting('unknown=green;sql_field=blue'),
  89
+                          dict(PALETTES[NOCOLOR_PALETTE],
  90
+                            SQL_FIELD={'fg':'blue'}))
  91
+
  92
+    def test_bad_color(self):
  93
+        self.assertEquals(parse_color_setting('error='), None)
  94
+        self.assertEquals(parse_color_setting('error=;sql_field=blue'),
  95
+                          dict(PALETTES[NOCOLOR_PALETTE],
  96
+                            SQL_FIELD={'fg':'blue'}))
  97
+        self.assertEquals(parse_color_setting('error=unknown'), None)
  98
+        self.assertEquals(parse_color_setting('error=unknown;sql_field=blue'),
  99
+                          dict(PALETTES[NOCOLOR_PALETTE],
  100
+                            SQL_FIELD={'fg':'blue'}))
  101
+        self.assertEquals(parse_color_setting('error=green/unknown'),
  102
+                          dict(PALETTES[NOCOLOR_PALETTE],
  103
+                            ERROR={'fg':'green'}))
  104
+        self.assertEquals(parse_color_setting('error=green/blue/something'),
  105
+                          dict(PALETTES[NOCOLOR_PALETTE],
  106
+                            ERROR={'fg':'green', 'bg': 'blue'}))
  107
+        self.assertEquals(parse_color_setting('error=green/blue/something,blink'),
  108
+                          dict(PALETTES[NOCOLOR_PALETTE],
  109
+                            ERROR={'fg':'green', 'bg': 'blue', 'opts': ('blink',)}))
  110
+
  111
+    def test_bad_option(self):
  112
+        self.assertEquals(parse_color_setting('error=green,unknown'),
  113
+                          dict(PALETTES[NOCOLOR_PALETTE],
  114
+                            ERROR={'fg':'green'}))
  115
+        self.assertEquals(parse_color_setting('error=green,unknown,blink'),
  116
+                          dict(PALETTES[NOCOLOR_PALETTE],
  117
+                            ERROR={'fg':'green', 'opts': ('blink',)}))
  118
+
  119
+    def test_role_case(self):
  120
+        self.assertEquals(parse_color_setting('ERROR=green'),
  121
+                          dict(PALETTES[NOCOLOR_PALETTE],
  122
+                            ERROR={'fg':'green'}))
  123
+        self.assertEquals(parse_color_setting('eRrOr=green'),
  124
+                          dict(PALETTES[NOCOLOR_PALETTE],
  125
+                            ERROR={'fg':'green'}))
  126
+
  127
+    def test_color_case(self):
  128
+        self.assertEquals(parse_color_setting('error=GREEN'),
  129
+                          dict(PALETTES[NOCOLOR_PALETTE],
  130
+                            ERROR={'fg':'green'}))
  131
+        self.assertEquals(parse_color_setting('error=GREEN/BLUE'),
  132
+                          dict(PALETTES[NOCOLOR_PALETTE],
  133
+                            ERROR={'fg':'green', 'bg':'blue'}))
  134
+
  135
+        self.assertEquals(parse_color_setting('error=gReEn'),
  136
+                          dict(PALETTES[NOCOLOR_PALETTE],
  137
+                            ERROR={'fg':'green'}))
  138
+        self.assertEquals(parse_color_setting('error=gReEn/bLuE'),
  139
+                          dict(PALETTES[NOCOLOR_PALETTE],
  140
+                            ERROR={'fg':'green', 'bg':'blue'}))
  141
+
  142
+    def test_opts_case(self):
  143
+        self.assertEquals(parse_color_setting('error=green,BLINK'),
  144
+                          dict(PALETTES[NOCOLOR_PALETTE],
  145
+                            ERROR={'fg':'green', 'opts': ('blink',)}))
  146
+
  147
+        self.assertEquals(parse_color_setting('error=green,bLiNk'),
  148
+                          dict(PALETTES[NOCOLOR_PALETTE],
  149
+                            ERROR={'fg':'green', 'opts': ('blink',)}))
3  tests/regressiontests/utils/tests.py
@@ -9,8 +9,8 @@
9 9
 
10 10
 import timesince
11 11
 import datastructures
12  
-import dateformat
13 12
 import itercompat
  13
+
14 14
 from decorators import DecoratorFromMiddlewareTests
15 15
 
16 16
 # We need this because "datastructures" uses sorted() and the tests are run in
@@ -28,6 +28,7 @@
28 28
 }
29 29
 
30 30
 from dateformat import *
  31
+from termcolors import *
31 32
 
32 33
 class TestUtilsHtml(TestCase):
33 34
 

0 notes on commit c38d66a

Please sign in to comment.
Something went wrong with that request. Please try again.