Mercurial > hg
comparison hgext/color.py @ 5787:b7b22a2ade2e
Add colored output to status and qseries commands
author | Kevin Christen <kevin.christen@gmail.com> |
---|---|
date | Mon, 31 Dec 2007 09:15:39 -0600 |
parents | |
children | 956e01c31914 |
comparison
equal
deleted
inserted
replaced
5786:c69ef6fdb092 | 5787:b7b22a2ade2e |
---|---|
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) |