##// END OF EJS Templates
color: colorize diff --stat
Brodie Rao -
r9641:9b99f158 default
parent child Browse files
Show More
@@ -1,282 +1,298 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
60
61 import os, sys
61 import os, sys
62
62
63 from mercurial import cmdutil, commands, extensions, error
63 from mercurial import cmdutil, commands, extensions, error
64 from mercurial.i18n import _
64 from mercurial.i18n import _
65
65
66 # start and stop parameters for effects
66 # start and stop parameters for effects
67 _effect_params = {'none': 0,
67 _effect_params = {'none': 0,
68 'black': 30,
68 'black': 30,
69 'red': 31,
69 'red': 31,
70 'green': 32,
70 'green': 32,
71 'yellow': 33,
71 'yellow': 33,
72 'blue': 34,
72 'blue': 34,
73 'magenta': 35,
73 'magenta': 35,
74 'cyan': 36,
74 'cyan': 36,
75 'white': 37,
75 'white': 37,
76 'bold': 1,
76 'bold': 1,
77 'italic': 3,
77 'italic': 3,
78 'underline': 4,
78 'underline': 4,
79 'inverse': 7,
79 'inverse': 7,
80 'black_background': 40,
80 'black_background': 40,
81 'red_background': 41,
81 'red_background': 41,
82 'green_background': 42,
82 'green_background': 42,
83 'yellow_background': 43,
83 'yellow_background': 43,
84 'blue_background': 44,
84 'blue_background': 44,
85 'purple_background': 45,
85 'purple_background': 45,
86 'cyan_background': 46,
86 'cyan_background': 46,
87 'white_background': 47}
87 'white_background': 47}
88
88
89 def render_effects(text, effects):
89 def render_effects(text, effects):
90 'Wrap text in commands to turn on each effect.'
90 'Wrap text in commands to turn on each effect.'
91 start = [str(_effect_params[e]) for e in ['none'] + effects]
91 start = [str(_effect_params[e]) for e in ['none'] + effects]
92 start = '\033[' + ';'.join(start) + 'm'
92 start = '\033[' + ';'.join(start) + 'm'
93 stop = '\033[' + str(_effect_params['none']) + 'm'
93 stop = '\033[' + str(_effect_params['none']) + 'm'
94 return ''.join([start, text, stop])
94 return ''.join([start, text, stop])
95
95
96 def colorstatus(orig, ui, repo, *pats, **opts):
96 def colorstatus(orig, ui, repo, *pats, **opts):
97 '''run the status command with colored output'''
97 '''run the status command with colored output'''
98
98
99 delimiter = opts['print0'] and '\0' or '\n'
99 delimiter = opts['print0'] and '\0' or '\n'
100
100
101 nostatus = opts.get('no_status')
101 nostatus = opts.get('no_status')
102 opts['no_status'] = False
102 opts['no_status'] = False
103 # run status and capture its output
103 # run status and capture its output
104 ui.pushbuffer()
104 ui.pushbuffer()
105 retval = orig(ui, repo, *pats, **opts)
105 retval = orig(ui, repo, *pats, **opts)
106 # filter out empty strings
106 # filter out empty strings
107 lines_with_status = [ line for line in ui.popbuffer().split(delimiter) if line ]
107 lines_with_status = [ line for line in ui.popbuffer().split(delimiter) if line ]
108
108
109 if nostatus:
109 if nostatus:
110 lines = [l[2:] for l in lines_with_status]
110 lines = [l[2:] for l in lines_with_status]
111 else:
111 else:
112 lines = lines_with_status
112 lines = lines_with_status
113
113
114 # apply color to output and display it
114 # apply color to output and display it
115 for i in xrange(len(lines)):
115 for i in xrange(len(lines)):
116 status = _status_abbreviations[lines_with_status[i][0]]
116 status = _status_abbreviations[lines_with_status[i][0]]
117 effects = _status_effects[status]
117 effects = _status_effects[status]
118 if effects:
118 if effects:
119 lines[i] = render_effects(lines[i], effects)
119 lines[i] = render_effects(lines[i], effects)
120 ui.write(lines[i] + delimiter)
120 ui.write(lines[i] + delimiter)
121 return retval
121 return retval
122
122
123 _status_abbreviations = { 'M': 'modified',
123 _status_abbreviations = { 'M': 'modified',
124 'A': 'added',
124 'A': 'added',
125 'R': 'removed',
125 'R': 'removed',
126 '!': 'deleted',
126 '!': 'deleted',
127 '?': 'unknown',
127 '?': 'unknown',
128 'I': 'ignored',
128 'I': 'ignored',
129 'C': 'clean',
129 'C': 'clean',
130 ' ': 'copied', }
130 ' ': 'copied', }
131
131
132 _status_effects = { 'modified': ['blue', 'bold'],
132 _status_effects = { 'modified': ['blue', 'bold'],
133 'added': ['green', 'bold'],
133 'added': ['green', 'bold'],
134 'removed': ['red', 'bold'],
134 'removed': ['red', 'bold'],
135 'deleted': ['cyan', 'bold', 'underline'],
135 'deleted': ['cyan', 'bold', 'underline'],
136 'unknown': ['magenta', 'bold', 'underline'],
136 'unknown': ['magenta', 'bold', 'underline'],
137 'ignored': ['black', 'bold'],
137 'ignored': ['black', 'bold'],
138 'clean': ['none'],
138 'clean': ['none'],
139 'copied': ['none'], }
139 'copied': ['none'], }
140
140
141 def colorqseries(orig, ui, repo, *dummy, **opts):
141 def colorqseries(orig, ui, repo, *dummy, **opts):
142 '''run the qseries command with colored output'''
142 '''run the qseries command with colored output'''
143 ui.pushbuffer()
143 ui.pushbuffer()
144 retval = orig(ui, repo, **opts)
144 retval = orig(ui, repo, **opts)
145 patchlines = ui.popbuffer().splitlines()
145 patchlines = ui.popbuffer().splitlines()
146 patchnames = repo.mq.series
146 patchnames = repo.mq.series
147
147
148 for patch, patchname in zip(patchlines, patchnames):
148 for patch, patchname in zip(patchlines, patchnames):
149 if opts['missing']:
149 if opts['missing']:
150 effects = _patch_effects['missing']
150 effects = _patch_effects['missing']
151 # Determine if patch is applied.
151 # Determine if patch is applied.
152 elif [ applied for applied in repo.mq.applied
152 elif [ applied for applied in repo.mq.applied
153 if patchname == applied.name ]:
153 if patchname == applied.name ]:
154 effects = _patch_effects['applied']
154 effects = _patch_effects['applied']
155 else:
155 else:
156 effects = _patch_effects['unapplied']
156 effects = _patch_effects['unapplied']
157
157
158 patch = patch.replace(patchname, render_effects(patchname, effects), 1)
158 patch = patch.replace(patchname, render_effects(patchname, effects), 1)
159 ui.write(patch + '\n')
159 ui.write(patch + '\n')
160 return retval
160 return retval
161
161
162 _patch_effects = { 'applied': ['blue', 'bold', 'underline'],
162 _patch_effects = { 'applied': ['blue', 'bold', 'underline'],
163 'missing': ['red', 'bold'],
163 'missing': ['red', 'bold'],
164 'unapplied': ['black', 'bold'], }
164 'unapplied': ['black', 'bold'], }
165 def colorwrap(orig, *args):
165 def colorwrap(orig, *args):
166 '''wrap ui.write for colored diff output'''
166 '''wrap ui.write for colored diff output'''
167 def _colorize(s):
167 def _colorize(s):
168 lines = s.split('\n')
168 lines = s.split('\n')
169 for i, line in enumerate(lines):
169 for i, line in enumerate(lines):
170 stripline = line
170 stripline = line
171 if line and line[0] in '+-':
171 if line and line[0] in '+-':
172 # highlight trailing whitespace, but only in changed lines
172 # highlight trailing whitespace, but only in changed lines
173 stripline = line.rstrip()
173 stripline = line.rstrip()
174 for prefix, style in _diff_prefixes:
174 for prefix, style in _diff_prefixes:
175 if stripline.startswith(prefix):
175 if stripline.startswith(prefix):
176 lines[i] = render_effects(stripline, _diff_effects[style])
176 lines[i] = render_effects(stripline, _diff_effects[style])
177 break
177 break
178 if line != stripline:
178 if line != stripline:
179 lines[i] += render_effects(
179 lines[i] += render_effects(
180 line[len(stripline):], _diff_effects['trailingwhitespace'])
180 line[len(stripline):], _diff_effects['trailingwhitespace'])
181 return '\n'.join(lines)
181 return '\n'.join(lines)
182 orig(*[_colorize(s) for s in args])
182 orig(*[_colorize(s) for s in args])
183
183
184 def colorshowpatch(orig, self, node):
184 def colorshowpatch(orig, self, node):
185 '''wrap cmdutil.changeset_printer.showpatch with colored output'''
185 '''wrap cmdutil.changeset_printer.showpatch with colored output'''
186 oldwrite = extensions.wrapfunction(self.ui, 'write', colorwrap)
186 oldwrite = extensions.wrapfunction(self.ui, 'write', colorwrap)
187 try:
187 try:
188 orig(self, node)
188 orig(self, node)
189 finally:
189 finally:
190 self.ui.write = oldwrite
190 self.ui.write = oldwrite
191
191
192 def colordiffstat(orig, s):
193 lines = s.split('\n')
194 for i, line in enumerate(lines):
195 if line and line[-1] in '+-':
196 name, graph = line.rsplit(' ', 1)
197 graph = graph.replace('-',
198 render_effects('-', _diff_effects['deleted']))
199 graph = graph.replace('+',
200 render_effects('+', _diff_effects['inserted']))
201 lines[i] = ' '.join([name, graph])
202 orig('\n'.join(lines))
203
192 def colordiff(orig, ui, repo, *pats, **opts):
204 def colordiff(orig, ui, repo, *pats, **opts):
193 '''run the diff command with colored output'''
205 '''run the diff command with colored output'''
194 oldwrite = extensions.wrapfunction(ui, 'write', colorwrap)
206 if opts.get('stat'):
207 wrapper = colordiffstat
208 else:
209 wrapper = colorwrap
210 oldwrite = extensions.wrapfunction(ui, 'write', wrapper)
195 try:
211 try:
196 orig(ui, repo, *pats, **opts)
212 orig(ui, repo, *pats, **opts)
197 finally:
213 finally:
198 ui.write = oldwrite
214 ui.write = oldwrite
199
215
200 _diff_prefixes = [('diff', 'diffline'),
216 _diff_prefixes = [('diff', 'diffline'),
201 ('copy', 'extended'),
217 ('copy', 'extended'),
202 ('rename', 'extended'),
218 ('rename', 'extended'),
203 ('old', 'extended'),
219 ('old', 'extended'),
204 ('new', 'extended'),
220 ('new', 'extended'),
205 ('deleted', 'extended'),
221 ('deleted', 'extended'),
206 ('---', 'file_a'),
222 ('---', 'file_a'),
207 ('+++', 'file_b'),
223 ('+++', 'file_b'),
208 ('@', 'hunk'),
224 ('@', 'hunk'),
209 ('-', 'deleted'),
225 ('-', 'deleted'),
210 ('+', 'inserted')]
226 ('+', 'inserted')]
211
227
212 _diff_effects = {'diffline': ['bold'],
228 _diff_effects = {'diffline': ['bold'],
213 'extended': ['cyan', 'bold'],
229 'extended': ['cyan', 'bold'],
214 'file_a': ['red', 'bold'],
230 'file_a': ['red', 'bold'],
215 'file_b': ['green', 'bold'],
231 'file_b': ['green', 'bold'],
216 'hunk': ['magenta'],
232 'hunk': ['magenta'],
217 'deleted': ['red'],
233 'deleted': ['red'],
218 'inserted': ['green'],
234 'inserted': ['green'],
219 'changed': ['white'],
235 'changed': ['white'],
220 'trailingwhitespace': ['bold', 'red_background']}
236 'trailingwhitespace': ['bold', 'red_background']}
221
237
222 def uisetup(ui):
238 def uisetup(ui):
223 '''Initialize the extension.'''
239 '''Initialize the extension.'''
224 _setupcmd(ui, 'diff', commands.table, colordiff, _diff_effects)
240 _setupcmd(ui, 'diff', commands.table, colordiff, _diff_effects)
225 _setupcmd(ui, 'incoming', commands.table, None, _diff_effects)
241 _setupcmd(ui, 'incoming', commands.table, None, _diff_effects)
226 _setupcmd(ui, 'log', commands.table, None, _diff_effects)
242 _setupcmd(ui, 'log', commands.table, None, _diff_effects)
227 _setupcmd(ui, 'outgoing', commands.table, None, _diff_effects)
243 _setupcmd(ui, 'outgoing', commands.table, None, _diff_effects)
228 _setupcmd(ui, 'tip', commands.table, None, _diff_effects)
244 _setupcmd(ui, 'tip', commands.table, None, _diff_effects)
229 _setupcmd(ui, 'status', commands.table, colorstatus, _status_effects)
245 _setupcmd(ui, 'status', commands.table, colorstatus, _status_effects)
230
246
231 try:
247 try:
232 mq = extensions.find('mq')
248 mq = extensions.find('mq')
233 _setupcmd(ui, 'qdiff', mq.cmdtable, colordiff, _diff_effects)
249 _setupcmd(ui, 'qdiff', mq.cmdtable, colordiff, _diff_effects)
234 _setupcmd(ui, 'qseries', mq.cmdtable, colorqseries, _patch_effects)
250 _setupcmd(ui, 'qseries', mq.cmdtable, colorqseries, _patch_effects)
235 except KeyError:
251 except KeyError:
236 # The mq extension is not enabled
252 # The mq extension is not enabled
237 pass
253 pass
238
254
239 try:
255 try:
240 rec = extensions.find('record')
256 rec = extensions.find('record')
241 _setupcmd(ui, 'record', rec.cmdtable, colordiff, _diff_effects)
257 _setupcmd(ui, 'record', rec.cmdtable, colordiff, _diff_effects)
242 except KeyError:
258 except KeyError:
243 # The record extension is not enabled
259 # The record extension is not enabled
244 pass
260 pass
245
261
246 def _setupcmd(ui, cmd, table, func, effectsmap):
262 def _setupcmd(ui, cmd, table, func, effectsmap):
247 '''patch in command to command table and load effect map'''
263 '''patch in command to command table and load effect map'''
248 def nocolor(orig, *args, **opts):
264 def nocolor(orig, *args, **opts):
249
265
250 if (opts['no_color'] or opts['color'] == 'never' or
266 if (opts['no_color'] or opts['color'] == 'never' or
251 (opts['color'] == 'auto' and (os.environ.get('TERM') == 'dumb'
267 (opts['color'] == 'auto' and (os.environ.get('TERM') == 'dumb'
252 or not sys.__stdout__.isatty()))):
268 or not sys.__stdout__.isatty()))):
253 return orig(*args, **opts)
269 return orig(*args, **opts)
254
270
255 oldshowpatch = extensions.wrapfunction(cmdutil.changeset_printer,
271 oldshowpatch = extensions.wrapfunction(cmdutil.changeset_printer,
256 'showpatch', colorshowpatch)
272 'showpatch', colorshowpatch)
257 try:
273 try:
258 if func is not None:
274 if func is not None:
259 return func(orig, *args, **opts)
275 return func(orig, *args, **opts)
260 return orig(*args, **opts)
276 return orig(*args, **opts)
261 finally:
277 finally:
262 cmdutil.changeset_printer.showpatch = oldshowpatch
278 cmdutil.changeset_printer.showpatch = oldshowpatch
263
279
264 entry = extensions.wrapcommand(table, cmd, nocolor)
280 entry = extensions.wrapcommand(table, cmd, nocolor)
265 entry[1].extend([
281 entry[1].extend([
266 ('', 'color', 'auto', _("when to colorize (always, auto, or never)")),
282 ('', 'color', 'auto', _("when to colorize (always, auto, or never)")),
267 ('', 'no-color', None, _("don't colorize output (DEPRECATED)")),
283 ('', 'no-color', None, _("don't colorize output (DEPRECATED)")),
268 ])
284 ])
269
285
270 for status in effectsmap:
286 for status in effectsmap:
271 configkey = cmd + '.' + status
287 configkey = cmd + '.' + status
272 effects = ui.configlist('color', configkey)
288 effects = ui.configlist('color', configkey)
273 if effects:
289 if effects:
274 good = []
290 good = []
275 for e in effects:
291 for e in effects:
276 if e in _effect_params:
292 if e in _effect_params:
277 good.append(e)
293 good.append(e)
278 else:
294 else:
279 ui.warn(_("ignoring unknown color/effect %r "
295 ui.warn(_("ignoring unknown color/effect %r "
280 "(configured in color.%s)\n")
296 "(configured in color.%s)\n")
281 % (e, configkey))
297 % (e, configkey))
282 effectsmap[status] = good
298 effectsmap[status] = good
@@ -1,50 +1,53 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 echo "[extensions]" >> $HGRCPATH
3 echo "[extensions]" >> $HGRCPATH
4 echo "color=" >> $HGRCPATH
4 echo "color=" >> $HGRCPATH
5
5
6 hg init repo
6 hg init repo
7 cd repo
7 cd repo
8 cat > a <<EOF
8 cat > a <<EOF
9 c
9 c
10 c
10 c
11 a
11 a
12 a
12 a
13 b
13 b
14 a
14 a
15 a
15 a
16 c
16 c
17 c
17 c
18 EOF
18 EOF
19 hg ci -Am adda
19 hg ci -Am adda
20 cat > a <<EOF
20 cat > a <<EOF
21 c
21 c
22 c
22 c
23 a
23 a
24 a
24 a
25 dd
25 dd
26 a
26 a
27 a
27 a
28 c
28 c
29 c
29 c
30 EOF
30 EOF
31
31
32 echo '% default context'
32 echo '% default context'
33 hg diff --nodates --color=always
33 hg diff --nodates --color=always
34
34
35 echo '% --unified=2'
35 echo '% --unified=2'
36 hg diff --nodates -U 2 --color=always
36 hg diff --nodates -U 2 --color=always
37
37
38 echo '% diffstat'
39 hg diff --stat --color=always
40
38 echo "record=" >> $HGRCPATH
41 echo "record=" >> $HGRCPATH
39 echo "[ui]" >> $HGRCPATH
42 echo "[ui]" >> $HGRCPATH
40 echo "interactive=true" >> $HGRCPATH
43 echo "interactive=true" >> $HGRCPATH
41 echo "[diff]" >> $HGRCPATH
44 echo "[diff]" >> $HGRCPATH
42 echo "git=True" >> $HGRCPATH
45 echo "git=True" >> $HGRCPATH
43
46
44 echo % record
47 echo % record
45 chmod 0755 a
48 chmod 0755 a
46 hg record --color=always -m moda a <<EOF
49 hg record --color=always -m moda a <<EOF
47 y
50 y
48 y
51 y
49 EOF
52 EOF
50 echo
53 echo
@@ -1,40 +1,43 b''
1 adding a
1 adding a
2 % default context
2 % default context
3 diff -r cf9f4ba66af2 a
3 diff -r cf9f4ba66af2 a
4 --- a/a
4 --- a/a
5 +++ b/a
5 +++ b/a
6 @@ -2,7 +2,7 @@
6 @@ -2,7 +2,7 @@
7 c
7 c
8 a
8 a
9 a
9 a
10 -b
10 -b
11 +dd
11 +dd
12 a
12 a
13 a
13 a
14 c
14 c
15 % --unified=2
15 % --unified=2
16 diff -r cf9f4ba66af2 a
16 diff -r cf9f4ba66af2 a
17 --- a/a
17 --- a/a
18 +++ b/a
18 +++ b/a
19 @@ -3,5 +3,5 @@
19 @@ -3,5 +3,5 @@
20 a
20 a
21 a
21 a
22 -b
22 -b
23 +dd
23 +dd
24 a
24 a
25 a
25 a
26 % diffstat
27 a | 2 +-
28 1 files changed, 1 insertions(+), 1 deletions(-)
26 % record
29 % record
27 diff --git a/a b/a
30 diff --git a/a b/a
28 old mode 100644
31 old mode 100644
29 new mode 100755
32 new mode 100755
30 1 hunks, 2 lines changed
33 1 hunks, 2 lines changed
31 examine changes to 'a'? [Ynsfdaq?] @@ -2,7 +2,7 @@
34 examine changes to 'a'? [Ynsfdaq?] @@ -2,7 +2,7 @@
32 c
35 c
33 a
36 a
34 a
37 a
35 -b
38 -b
36 +dd
39 +dd
37 a
40 a
38 a
41 a
39 c
42 c
40 record this change to 'a'? [Ynsfdaq?]
43 record this change to 'a'? [Ynsfdaq?]
General Comments 0
You need to be logged in to leave comments. Login now