##// END OF EJS Templates
color: improve coding style
Benoit Boissinot -
r10222:98f630e1 default
parent child Browse files
Show More
@@ -1,343 +1,343 b''
1 # color.py color output for the status and qseries commands
1 # color.py color output for the status and qseries commands
2 #
2 #
3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com>
3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com>
4 #
4 #
5 # This program is free software; you can redistribute it and/or modify it
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
6 # under the terms of the GNU General Public License as published by the
7 # Free Software Foundation; either version 2 of the License, or (at your
7 # Free Software Foundation; either version 2 of the License, or (at your
8 # option) any later version.
8 # option) any later version.
9 #
9 #
10 # This program is distributed in the hope that it will be useful, but
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13 # Public License for more details.
13 # Public License for more details.
14 #
14 #
15 # You should have received a copy of the GNU General Public License along
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc.,
16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
18
19 '''colorize output from some commands
19 '''colorize output from some commands
20
20
21 This extension modifies the status command to add color to its output
21 This extension modifies the status command to add color to its output
22 to reflect file status, the qseries command to add color to reflect
22 to reflect file status, the qseries command to add color to reflect
23 patch status (applied, unapplied, missing), and to diff-related
23 patch status (applied, unapplied, missing), and to diff-related
24 commands to highlight additions, removals, diff headers, and trailing
24 commands to highlight additions, removals, diff headers, and trailing
25 whitespace.
25 whitespace.
26
26
27 Other effects in addition to color, like bold and underlined text, are
27 Other effects in addition to color, like bold and underlined text, are
28 also available. Effects are rendered with the ECMA-48 SGR control
28 also available. Effects are rendered with the ECMA-48 SGR control
29 function (aka ANSI escape codes). This module also provides the
29 function (aka ANSI escape codes). This module also provides the
30 render_text function, which can be used to add effects to any text.
30 render_text function, which can be used to add effects to any text.
31
31
32 Default effects may be overridden from the .hgrc file::
32 Default effects may be overridden from the .hgrc file::
33
33
34 [color]
34 [color]
35 status.modified = blue bold underline red_background
35 status.modified = blue bold underline red_background
36 status.added = green bold
36 status.added = green bold
37 status.removed = red bold blue_background
37 status.removed = red bold blue_background
38 status.deleted = cyan bold underline
38 status.deleted = cyan bold underline
39 status.unknown = magenta bold underline
39 status.unknown = magenta bold underline
40 status.ignored = black bold
40 status.ignored = black bold
41
41
42 # 'none' turns off all effects
42 # 'none' turns off all effects
43 status.clean = none
43 status.clean = none
44 status.copied = none
44 status.copied = none
45
45
46 qseries.applied = blue bold underline
46 qseries.applied = blue bold underline
47 qseries.unapplied = black bold
47 qseries.unapplied = black bold
48 qseries.missing = red bold
48 qseries.missing = red bold
49
49
50 diff.diffline = bold
50 diff.diffline = bold
51 diff.extended = cyan bold
51 diff.extended = cyan bold
52 diff.file_a = red bold
52 diff.file_a = red bold
53 diff.file_b = green bold
53 diff.file_b = green bold
54 diff.hunk = magenta
54 diff.hunk = magenta
55 diff.deleted = red
55 diff.deleted = red
56 diff.inserted = green
56 diff.inserted = green
57 diff.changed = white
57 diff.changed = white
58 diff.trailingwhitespace = bold red_background
58 diff.trailingwhitespace = bold red_background
59
59
60 bookmarks.current = green
60 bookmarks.current = green
61 '''
61 '''
62
62
63 import os, sys
63 import os, sys
64
64
65 from mercurial import cmdutil, commands, extensions, error
65 from mercurial import cmdutil, commands, extensions, error
66 from mercurial.i18n import _
66 from mercurial.i18n import _
67
67
68 # start and stop parameters for effects
68 # start and stop parameters for effects
69 _effect_params = {'none': 0,
69 _effect_params = {'none': 0,
70 'black': 30,
70 'black': 30,
71 'red': 31,
71 'red': 31,
72 'green': 32,
72 'green': 32,
73 'yellow': 33,
73 'yellow': 33,
74 'blue': 34,
74 'blue': 34,
75 'magenta': 35,
75 'magenta': 35,
76 'cyan': 36,
76 'cyan': 36,
77 'white': 37,
77 'white': 37,
78 'bold': 1,
78 'bold': 1,
79 'italic': 3,
79 'italic': 3,
80 'underline': 4,
80 'underline': 4,
81 'inverse': 7,
81 'inverse': 7,
82 'black_background': 40,
82 'black_background': 40,
83 'red_background': 41,
83 'red_background': 41,
84 'green_background': 42,
84 'green_background': 42,
85 'yellow_background': 43,
85 'yellow_background': 43,
86 'blue_background': 44,
86 'blue_background': 44,
87 'purple_background': 45,
87 'purple_background': 45,
88 'cyan_background': 46,
88 'cyan_background': 46,
89 'white_background': 47}
89 'white_background': 47}
90
90
91 def render_effects(text, effects):
91 def render_effects(text, effects):
92 'Wrap text in commands to turn on each effect.'
92 'Wrap text in commands to turn on each effect.'
93 start = [str(_effect_params[e]) for e in ['none'] + effects]
93 start = [str(_effect_params[e]) for e in ['none'] + effects]
94 start = '\033[' + ';'.join(start) + 'm'
94 start = '\033[' + ';'.join(start) + 'm'
95 stop = '\033[' + str(_effect_params['none']) + 'm'
95 stop = '\033[' + str(_effect_params['none']) + 'm'
96 return ''.join([start, text, stop])
96 return ''.join([start, text, stop])
97
97
98 def colorstatus(orig, ui, repo, *pats, **opts):
98 def colorstatus(orig, ui, repo, *pats, **opts):
99 '''run the status command with colored output'''
99 '''run the status command with colored output'''
100
100
101 delimiter = opts['print0'] and '\0' or '\n'
101 delimiter = opts['print0'] and '\0' or '\n'
102
102
103 nostatus = opts.get('no_status')
103 nostatus = opts.get('no_status')
104 opts['no_status'] = False
104 opts['no_status'] = False
105 # run status and capture its output
105 # run status and capture its output
106 ui.pushbuffer()
106 ui.pushbuffer()
107 retval = orig(ui, repo, *pats, **opts)
107 retval = orig(ui, repo, *pats, **opts)
108 # filter out empty strings
108 # filter out empty strings
109 lines_with_status = [ line for line in ui.popbuffer().split(delimiter) if line ]
109 lines_with_status = [line for line in ui.popbuffer().split(delimiter) if line]
110
110
111 if nostatus:
111 if nostatus:
112 lines = [l[2:] for l in lines_with_status]
112 lines = [l[2:] for l in lines_with_status]
113 else:
113 else:
114 lines = lines_with_status
114 lines = lines_with_status
115
115
116 # apply color to output and display it
116 # apply color to output and display it
117 for i in xrange(len(lines)):
117 for i in xrange(len(lines)):
118 status = _status_abbreviations[lines_with_status[i][0]]
118 status = _status_abbreviations[lines_with_status[i][0]]
119 effects = _status_effects[status]
119 effects = _status_effects[status]
120 if effects:
120 if effects:
121 lines[i] = render_effects(lines[i], effects)
121 lines[i] = render_effects(lines[i], effects)
122 ui.write(lines[i] + delimiter)
122 ui.write(lines[i] + delimiter)
123 return retval
123 return retval
124
124
125 _status_abbreviations = { 'M': 'modified',
125 _status_abbreviations = { 'M': 'modified',
126 'A': 'added',
126 'A': 'added',
127 'R': 'removed',
127 'R': 'removed',
128 '!': 'deleted',
128 '!': 'deleted',
129 '?': 'unknown',
129 '?': 'unknown',
130 'I': 'ignored',
130 'I': 'ignored',
131 'C': 'clean',
131 'C': 'clean',
132 ' ': 'copied', }
132 ' ': 'copied', }
133
133
134 _status_effects = { 'modified': ['blue', 'bold'],
134 _status_effects = { 'modified': ['blue', 'bold'],
135 'added': ['green', 'bold'],
135 'added': ['green', 'bold'],
136 'removed': ['red', 'bold'],
136 'removed': ['red', 'bold'],
137 'deleted': ['cyan', 'bold', 'underline'],
137 'deleted': ['cyan', 'bold', 'underline'],
138 'unknown': ['magenta', 'bold', 'underline'],
138 'unknown': ['magenta', 'bold', 'underline'],
139 'ignored': ['black', 'bold'],
139 'ignored': ['black', 'bold'],
140 'clean': ['none'],
140 'clean': ['none'],
141 'copied': ['none'], }
141 'copied': ['none'], }
142
142
143 _bookmark_effects = { 'current': ['green'] }
143 _bookmark_effects = { 'current': ['green'] }
144
144
145 def colorbookmarks(orig, ui, repo, *pats, **opts):
145 def colorbookmarks(orig, ui, repo, *pats, **opts):
146 def colorize(orig, s):
146 def colorize(orig, s):
147 lines = s.split('\n')
147 lines = s.split('\n')
148 for i, line in enumerate(lines):
148 for i, line in enumerate(lines):
149 if line.startswith(" *"):
149 if line.startswith(" *"):
150 lines[i] = render_effects(line, _bookmark_effects['current'])
150 lines[i] = render_effects(line, _bookmark_effects['current'])
151 orig('\n'.join(lines))
151 orig('\n'.join(lines))
152 oldwrite = extensions.wrapfunction(ui, 'write', colorize)
152 oldwrite = extensions.wrapfunction(ui, 'write', colorize)
153 try:
153 try:
154 orig(ui, repo, *pats, **opts)
154 orig(ui, repo, *pats, **opts)
155 finally:
155 finally:
156 ui.write = oldwrite
156 ui.write = oldwrite
157
157
158 def colorqseries(orig, ui, repo, *dummy, **opts):
158 def colorqseries(orig, ui, repo, *dummy, **opts):
159 '''run the qseries command with colored output'''
159 '''run the qseries command with colored output'''
160 ui.pushbuffer()
160 ui.pushbuffer()
161 retval = orig(ui, repo, **opts)
161 retval = orig(ui, repo, **opts)
162 patchlines = ui.popbuffer().splitlines()
162 patchlines = ui.popbuffer().splitlines()
163 patchnames = repo.mq.series
163 patchnames = repo.mq.series
164
164
165 for patch, patchname in zip(patchlines, patchnames):
165 for patch, patchname in zip(patchlines, patchnames):
166 if opts['missing']:
166 if opts['missing']:
167 effects = _patch_effects['missing']
167 effects = _patch_effects['missing']
168 # Determine if patch is applied.
168 # Determine if patch is applied.
169 elif [ applied for applied in repo.mq.applied
169 elif [ applied for applied in repo.mq.applied
170 if patchname == applied.name ]:
170 if patchname == applied.name ]:
171 effects = _patch_effects['applied']
171 effects = _patch_effects['applied']
172 else:
172 else:
173 effects = _patch_effects['unapplied']
173 effects = _patch_effects['unapplied']
174
174
175 patch = patch.replace(patchname, render_effects(patchname, effects), 1)
175 patch = patch.replace(patchname, render_effects(patchname, effects), 1)
176 ui.write(patch + '\n')
176 ui.write(patch + '\n')
177 return retval
177 return retval
178
178
179 _patch_effects = { 'applied': ['blue', 'bold', 'underline'],
179 _patch_effects = { 'applied': ['blue', 'bold', 'underline'],
180 'missing': ['red', 'bold'],
180 'missing': ['red', 'bold'],
181 'unapplied': ['black', 'bold'], }
181 'unapplied': ['black', 'bold'], }
182 def colorwrap(orig, *args):
182 def colorwrap(orig, *args):
183 '''wrap ui.write for colored diff output'''
183 '''wrap ui.write for colored diff output'''
184 def _colorize(s):
184 def _colorize(s):
185 lines = s.split('\n')
185 lines = s.split('\n')
186 for i, line in enumerate(lines):
186 for i, line in enumerate(lines):
187 stripline = line
187 stripline = line
188 if line and line[0] in '+-':
188 if line and line[0] in '+-':
189 # highlight trailing whitespace, but only in changed lines
189 # highlight trailing whitespace, but only in changed lines
190 stripline = line.rstrip()
190 stripline = line.rstrip()
191 for prefix, style in _diff_prefixes:
191 for prefix, style in _diff_prefixes:
192 if stripline.startswith(prefix):
192 if stripline.startswith(prefix):
193 lines[i] = render_effects(stripline, _diff_effects[style])
193 lines[i] = render_effects(stripline, _diff_effects[style])
194 break
194 break
195 if line != stripline:
195 if line != stripline:
196 lines[i] += render_effects(
196 lines[i] += render_effects(
197 line[len(stripline):], _diff_effects['trailingwhitespace'])
197 line[len(stripline):], _diff_effects['trailingwhitespace'])
198 return '\n'.join(lines)
198 return '\n'.join(lines)
199 orig(*[_colorize(s) for s in args])
199 orig(*[_colorize(s) for s in args])
200
200
201 def colorshowpatch(orig, self, node):
201 def colorshowpatch(orig, self, node):
202 '''wrap cmdutil.changeset_printer.showpatch with colored output'''
202 '''wrap cmdutil.changeset_printer.showpatch with colored output'''
203 oldwrite = extensions.wrapfunction(self.ui, 'write', colorwrap)
203 oldwrite = extensions.wrapfunction(self.ui, 'write', colorwrap)
204 try:
204 try:
205 orig(self, node)
205 orig(self, node)
206 finally:
206 finally:
207 self.ui.write = oldwrite
207 self.ui.write = oldwrite
208
208
209 def colordiffstat(orig, s):
209 def colordiffstat(orig, s):
210 lines = s.split('\n')
210 lines = s.split('\n')
211 for i, line in enumerate(lines):
211 for i, line in enumerate(lines):
212 if line and line[-1] in '+-':
212 if line and line[-1] in '+-':
213 name, graph = line.rsplit(' ', 1)
213 name, graph = line.rsplit(' ', 1)
214 graph = graph.replace('-',
214 graph = graph.replace('-',
215 render_effects('-', _diff_effects['deleted']))
215 render_effects('-', _diff_effects['deleted']))
216 graph = graph.replace('+',
216 graph = graph.replace('+',
217 render_effects('+', _diff_effects['inserted']))
217 render_effects('+', _diff_effects['inserted']))
218 lines[i] = ' '.join([name, graph])
218 lines[i] = ' '.join([name, graph])
219 orig('\n'.join(lines))
219 orig('\n'.join(lines))
220
220
221 def colordiff(orig, ui, repo, *pats, **opts):
221 def colordiff(orig, ui, repo, *pats, **opts):
222 '''run the diff command with colored output'''
222 '''run the diff command with colored output'''
223 if opts.get('stat'):
223 if opts.get('stat'):
224 wrapper = colordiffstat
224 wrapper = colordiffstat
225 else:
225 else:
226 wrapper = colorwrap
226 wrapper = colorwrap
227 oldwrite = extensions.wrapfunction(ui, 'write', wrapper)
227 oldwrite = extensions.wrapfunction(ui, 'write', wrapper)
228 try:
228 try:
229 orig(ui, repo, *pats, **opts)
229 orig(ui, repo, *pats, **opts)
230 finally:
230 finally:
231 ui.write = oldwrite
231 ui.write = oldwrite
232
232
233 def colorchurn(orig, ui, repo, *pats, **opts):
233 def colorchurn(orig, ui, repo, *pats, **opts):
234 '''run the churn command with colored output'''
234 '''run the churn command with colored output'''
235 if not opts.get('diffstat'):
235 if not opts.get('diffstat'):
236 return orig(ui, repo, *pats, **opts)
236 return orig(ui, repo, *pats, **opts)
237 oldwrite = extensions.wrapfunction(ui, 'write', colordiffstat)
237 oldwrite = extensions.wrapfunction(ui, 'write', colordiffstat)
238 try:
238 try:
239 orig(ui, repo, *pats, **opts)
239 orig(ui, repo, *pats, **opts)
240 finally:
240 finally:
241 ui.write = oldwrite
241 ui.write = oldwrite
242
242
243 _diff_prefixes = [('diff', 'diffline'),
243 _diff_prefixes = [('diff', 'diffline'),
244 ('copy', 'extended'),
244 ('copy', 'extended'),
245 ('rename', 'extended'),
245 ('rename', 'extended'),
246 ('old', 'extended'),
246 ('old', 'extended'),
247 ('new', 'extended'),
247 ('new', 'extended'),
248 ('deleted', 'extended'),
248 ('deleted', 'extended'),
249 ('---', 'file_a'),
249 ('---', 'file_a'),
250 ('+++', 'file_b'),
250 ('+++', 'file_b'),
251 ('@', 'hunk'),
251 ('@', 'hunk'),
252 ('-', 'deleted'),
252 ('-', 'deleted'),
253 ('+', 'inserted')]
253 ('+', 'inserted')]
254
254
255 _diff_effects = {'diffline': ['bold'],
255 _diff_effects = {'diffline': ['bold'],
256 'extended': ['cyan', 'bold'],
256 'extended': ['cyan', 'bold'],
257 'file_a': ['red', 'bold'],
257 'file_a': ['red', 'bold'],
258 'file_b': ['green', 'bold'],
258 'file_b': ['green', 'bold'],
259 'hunk': ['magenta'],
259 'hunk': ['magenta'],
260 'deleted': ['red'],
260 'deleted': ['red'],
261 'inserted': ['green'],
261 'inserted': ['green'],
262 'changed': ['white'],
262 'changed': ['white'],
263 'trailingwhitespace': ['bold', 'red_background']}
263 'trailingwhitespace': ['bold', 'red_background']}
264
264
265 def extsetup(ui):
265 def extsetup(ui):
266 '''Initialize the extension.'''
266 '''Initialize the extension.'''
267 _setupcmd(ui, 'diff', commands.table, colordiff, _diff_effects)
267 _setupcmd(ui, 'diff', commands.table, colordiff, _diff_effects)
268 _setupcmd(ui, 'incoming', commands.table, None, _diff_effects)
268 _setupcmd(ui, 'incoming', commands.table, None, _diff_effects)
269 _setupcmd(ui, 'log', commands.table, None, _diff_effects)
269 _setupcmd(ui, 'log', commands.table, None, _diff_effects)
270 _setupcmd(ui, 'outgoing', commands.table, None, _diff_effects)
270 _setupcmd(ui, 'outgoing', commands.table, None, _diff_effects)
271 _setupcmd(ui, 'tip', commands.table, None, _diff_effects)
271 _setupcmd(ui, 'tip', commands.table, None, _diff_effects)
272 _setupcmd(ui, 'status', commands.table, colorstatus, _status_effects)
272 _setupcmd(ui, 'status', commands.table, colorstatus, _status_effects)
273
273
274 try:
274 try:
275 mq = extensions.find('mq')
275 mq = extensions.find('mq')
276 _setupcmd(ui, 'qdiff', mq.cmdtable, colordiff, _diff_effects)
276 _setupcmd(ui, 'qdiff', mq.cmdtable, colordiff, _diff_effects)
277 _setupcmd(ui, 'qseries', mq.cmdtable, colorqseries, _patch_effects)
277 _setupcmd(ui, 'qseries', mq.cmdtable, colorqseries, _patch_effects)
278 except KeyError:
278 except KeyError:
279 mq = None
279 mq = None
280
280
281 try:
281 try:
282 rec = extensions.find('record')
282 rec = extensions.find('record')
283 _setupcmd(ui, 'record', rec.cmdtable, colordiff, _diff_effects)
283 _setupcmd(ui, 'record', rec.cmdtable, colordiff, _diff_effects)
284 except KeyError:
284 except KeyError:
285 rec = None
285 rec = None
286
286
287 if mq and rec:
287 if mq and rec:
288 _setupcmd(ui, 'qrecord', rec.cmdtable, colordiff, _diff_effects)
288 _setupcmd(ui, 'qrecord', rec.cmdtable, colordiff, _diff_effects)
289 try:
289 try:
290 churn = extensions.find('churn')
290 churn = extensions.find('churn')
291 _setupcmd(ui, 'churn', churn.cmdtable, colorchurn, _diff_effects)
291 _setupcmd(ui, 'churn', churn.cmdtable, colorchurn, _diff_effects)
292 except KeyError:
292 except KeyError:
293 churn = None
293 churn = None
294
294
295 try:
295 try:
296 bookmarks = extensions.find('bookmarks')
296 bookmarks = extensions.find('bookmarks')
297 _setupcmd(ui, 'bookmarks', bookmarks.cmdtable, colorbookmarks,
297 _setupcmd(ui, 'bookmarks', bookmarks.cmdtable, colorbookmarks,
298 _bookmark_effects)
298 _bookmark_effects)
299 except KeyError:
299 except KeyError:
300 # The bookmarks extension is not enabled
300 # The bookmarks extension is not enabled
301 pass
301 pass
302
302
303 def _setupcmd(ui, cmd, table, func, effectsmap):
303 def _setupcmd(ui, cmd, table, func, effectsmap):
304 '''patch in command to command table and load effect map'''
304 '''patch in command to command table and load effect map'''
305 def nocolor(orig, *args, **opts):
305 def nocolor(orig, *args, **opts):
306
306
307 if (opts['no_color'] or opts['color'] == 'never' or
307 if (opts['no_color'] or opts['color'] == 'never' or
308 (opts['color'] == 'auto' and (os.environ.get('TERM') == 'dumb'
308 (opts['color'] == 'auto' and (os.environ.get('TERM') == 'dumb'
309 or not sys.__stdout__.isatty()))):
309 or not sys.__stdout__.isatty()))):
310 del opts['no_color']
310 del opts['no_color']
311 del opts['color']
311 del opts['color']
312 return orig(*args, **opts)
312 return orig(*args, **opts)
313
313
314 oldshowpatch = extensions.wrapfunction(cmdutil.changeset_printer,
314 oldshowpatch = extensions.wrapfunction(cmdutil.changeset_printer,
315 'showpatch', colorshowpatch)
315 'showpatch', colorshowpatch)
316 del opts['no_color']
316 del opts['no_color']
317 del opts['color']
317 del opts['color']
318 try:
318 try:
319 if func is not None:
319 if func is not None:
320 return func(orig, *args, **opts)
320 return func(orig, *args, **opts)
321 return orig(*args, **opts)
321 return orig(*args, **opts)
322 finally:
322 finally:
323 cmdutil.changeset_printer.showpatch = oldshowpatch
323 cmdutil.changeset_printer.showpatch = oldshowpatch
324
324
325 entry = extensions.wrapcommand(table, cmd, nocolor)
325 entry = extensions.wrapcommand(table, cmd, nocolor)
326 entry[1].extend([
326 entry[1].extend([
327 ('', 'color', 'auto', _("when to colorize (always, auto, or never)")),
327 ('', 'color', 'auto', _("when to colorize (always, auto, or never)")),
328 ('', 'no-color', None, _("don't colorize output (DEPRECATED)")),
328 ('', 'no-color', None, _("don't colorize output (DEPRECATED)")),
329 ])
329 ])
330
330
331 for status in effectsmap:
331 for status in effectsmap:
332 configkey = cmd + '.' + status
332 configkey = cmd + '.' + status
333 effects = ui.configlist('color', configkey)
333 effects = ui.configlist('color', configkey)
334 if effects:
334 if effects:
335 good = []
335 good = []
336 for e in effects:
336 for e in effects:
337 if e in _effect_params:
337 if e in _effect_params:
338 good.append(e)
338 good.append(e)
339 else:
339 else:
340 ui.warn(_("ignoring unknown color/effect %r "
340 ui.warn(_("ignoring unknown color/effect %r "
341 "(configured in color.%s)\n")
341 "(configured in color.%s)\n")
342 % (e, configkey))
342 % (e, configkey))
343 effectsmap[status] = good
343 effectsmap[status] = good
General Comments 0
You need to be logged in to leave comments. Login now