##// END OF EJS Templates
Add colored output to status and qseries commands
Kevin Christen -
r5787:b7b22a2a default
parent child Browse files
Show More
@@ -0,0 +1,220
1 # color.py color output for the status and qseries commands
2 #
3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com>
4 #
5 # This program is free software: you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by the
7 # Free Software Foundation, either version 3 of the License, or (at your
8 # option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13 # Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License along
16 # with this program. If not, see <http://www.gnu.org/licenses/>. This
17 # software may be used and distributed according to the terms of the GNU
18 # General Public License, incorporated herein by reference.
19
20 '''add color output to the status and qseries commands
21
22 This extension modifies the status command to add color to its output to
23 reflect file status, and the qseries command to add color to reflect patch
24 status (applied, unapplied, missing). Other effects in addition to color,
25 like bold and underlined text, are also available. Effects are rendered
26 with the ECMA-48 SGR control function (aka ANSI escape codes). This module
27 also provides the render_text function, which can be used to add effects to
28 any text.
29
30 To enable this extension, add this to your .hgrc file:
31 [extensions]
32 color =
33
34 Default effects my be overriden from the .hgrc file:
35
36 [color]
37 status.modified = blue bold underline red_background
38 status.added = green bold
39 status.removed = red bold blue_background
40 status.deleted = cyan bold underline
41 status.unknown = magenta bold underline
42 status.ignored = black bold
43
44 'none' turns off all effects
45 status.clean = none
46 status.copied = none
47
48 qseries.applied = blue bold underline
49 qseries.unapplied = black bold
50 qseries.missing = red bold
51 '''
52
53 import re, sys
54
55 from mercurial import commands, cmdutil, ui
56 from mercurial.i18n import _
57
58 # start and stop parameters for effects
59 _effect_params = { 'none': (0, 0),
60 'black': (30, 39),
61 'red': (31, 39),
62 'green': (32, 39),
63 'yellow': (33, 39),
64 'blue': (34, 39),
65 'magenta': (35, 39),
66 'cyan': (36, 39),
67 'white': (37, 39),
68 'bold': (1, 22),
69 'italic': (3, 23),
70 'underline': (4, 24),
71 'inverse': (7, 27),
72 'black_background': (40, 49),
73 'red_background': (41, 49),
74 'green_background': (42, 49),
75 'yellow_background': (43, 49),
76 'blue_background': (44, 49),
77 'purple_background': (45, 49),
78 'cyan_background': (46, 49),
79 'white_background': (47, 49), }
80
81 def render_effects(text, *effects):
82 'Wrap text in commands to turn on each effect.'
83 start = []
84 stop = []
85 for effect in effects:
86 start.append(str(_effect_params[effect][0]))
87 stop.append(str(_effect_params[effect][1]))
88 start = '\033[' + ';'.join(start) + 'm'
89 stop = '\033[' + ';'.join(stop) + 'm'
90 return start + text + stop
91
92 def colorstatus(statusfunc, ui, repo, *pats, **opts):
93 '''run the status command with colored output'''
94
95 delimiter = opts['print0'] and '\0' or '\n'
96
97 # run status and capture it's output
98 ui.pushbuffer()
99 retval = statusfunc(ui, repo, *pats, **opts)
100 # filter out empty strings
101 lines = [ line for line in ui.popbuffer().split(delimiter) if line ]
102
103 if opts['no_status']:
104 # if --no-status, run the command again without that option to get
105 # output with status abbreviations
106 opts['no_status'] = False
107 ui.pushbuffer()
108 statusfunc(ui, repo, *pats, **opts)
109 # filter out empty strings
110 lines_with_status = [ line for
111 line in ui.popbuffer().split(delimiter) if line ]
112 else:
113 lines_with_status = lines
114
115 # apply color to output and display it
116 for i in xrange(0, len(lines)):
117 status = _status_abbreviations[lines_with_status[i][0]]
118 effects = _status_effects[status]
119 if effects:
120 lines[i] = render_effects(lines[i], *effects)
121 sys.stdout.write(lines[i] + delimiter)
122 return retval
123
124 _status_abbreviations = { 'M': 'modified',
125 'A': 'added',
126 'R': 'removed',
127 'D': 'deleted',
128 '?': 'unknown',
129 'I': 'ignored',
130 'C': 'clean',
131 ' ': 'copied', }
132
133 _status_effects = { 'modified': ('blue', 'bold'),
134 'added': ('green', 'bold'),
135 'removed': ('red', 'bold'),
136 'deleted': ('cyan', 'bold', 'underline'),
137 'unknown': ('magenta', 'bold', 'underline'),
138 'ignored': ('black', 'bold'),
139 'clean': ('none', ),
140 'copied': ('none', ), }
141
142 def colorqseries(qseriesfunc, ui, repo, *dummy, **opts):
143 '''run the qseries command with colored output'''
144 ui.pushbuffer()
145 retval = qseriesfunc(ui, repo, **opts)
146 patches = ui.popbuffer().splitlines()
147 for patch in patches:
148 if opts['missing']:
149 effects = _patch_effects['missing']
150 # Determine if patch is applied. Search for beginning of output
151 # line in the applied patch list, in case --summary has been used
152 # and output line isn't just the patch name.
153 elif [ applied for applied in repo.mq.applied
154 if patch.startswith(applied.name) ]:
155 effects = _patch_effects['applied']
156 else:
157 effects = _patch_effects['unapplied']
158 sys.stdout.write(render_effects(patch, *effects) + '\n')
159 return retval
160
161 _patch_effects = { 'applied': ('blue', 'bold', 'underline'),
162 'missing': ('red', 'bold'),
163 'unapplied': ('black', 'bold'), }
164
165 def uisetup(ui):
166 '''Initialize the extension.'''
167 nocoloropt = ('', 'no-color', None, _("don't colorize output"))
168 _decoratecmd(ui, 'status', commands.table, colorstatus, nocoloropt)
169 _configcmdeffects(ui, 'status', _status_effects);
170 if ui.config('extensions', 'hgext.mq', default=None) is not None:
171 from hgext import mq
172 _decoratecmd(ui, 'qseries', mq.cmdtable, colorqseries, nocoloropt)
173 _configcmdeffects(ui, 'qseries', _patch_effects);
174
175 def _decoratecmd(ui, cmd, table, delegate, *delegateoptions):
176 '''Replace the function that implements cmd in table with a decorator.
177
178 The decorator that becomes the new implementation of cmd calls
179 delegate. The delegate's first argument is the replaced function,
180 followed by the normal Mercurial command arguments (ui, repo, ...). If
181 the delegate adds command options, supply them as delegateoptions.
182 '''
183 cmdkey, cmdentry = _cmdtableitem(ui, cmd, table)
184 decorator = lambda ui, repo, *args, **opts: \
185 _colordecorator(delegate, cmdentry[0],
186 ui, repo, *args, **opts)
187 # make sure 'hg help cmd' still works
188 decorator.__doc__ = cmdentry[0].__doc__
189 decoratorentry = (decorator,) + cmdentry[1:]
190 for option in delegateoptions:
191 decoratorentry[1].append(option)
192 table[cmdkey] = decoratorentry
193
194 def _cmdtableitem(ui, cmd, table):
195 '''Return key, value from table for cmd, or None if not found.'''
196 aliases, entry = cmdutil.findcmd(ui, cmd, table)
197 for candidatekey, candidateentry in table.iteritems():
198 if candidateentry is entry:
199 return candidatekey, entry
200
201 def _colordecorator(colorfunc, nocolorfunc, ui, repo, *args, **opts):
202 '''Delegate to colorfunc or nocolorfunc, depending on conditions.
203
204 Delegate to colorfunc unless --no-color option is set or output is not
205 to a tty.
206 '''
207 if opts['no_color'] or not sys.stdout.isatty():
208 return nocolorfunc(ui, repo, *args, **opts)
209 return colorfunc(nocolorfunc, ui, repo, *args, **opts)
210
211 def _configcmdeffects(ui, cmdname, effectsmap):
212 '''Override default effects for cmdname with those from .hgrc file.
213
214 Entries in the .hgrc file are in the [color] section, and look like
215 'cmdname'.'status' (for instance, 'status.modified = blue bold inverse').
216 '''
217 for status in effectsmap:
218 effects = ui.config('color', cmdname + '.' + status)
219 if effects:
220 effectsmap[status] = re.split('\W+', effects)
General Comments 0
You need to be logged in to leave comments. Login now