##// END OF EJS Templates
Sprinkle deprecation warnings stacklevel in the debugger....
Matthias Bussonnier -
Show More
@@ -1,629 +1,629 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 Pdb debugger class.
4 4
5 5 Modified from the standard pdb.Pdb class to avoid including readline, so that
6 6 the command line completion of other programs which include this isn't
7 7 damaged.
8 8
9 9 In the future, this class will be expanded with improvements over the standard
10 10 pdb.
11 11
12 12 The code in this file is mainly lifted out of cmd.py in Python 2.2, with minor
13 13 changes. Licensing should therefore be under the standard Python terms. For
14 14 details on the PSF (Python Software Foundation) standard license, see:
15 15
16 16 http://www.python.org/2.2.3/license.html"""
17 17
18 18 #*****************************************************************************
19 19 #
20 20 # This file is licensed under the PSF license.
21 21 #
22 22 # Copyright (C) 2001 Python Software Foundation, www.python.org
23 23 # Copyright (C) 2005-2006 Fernando Perez. <fperez@colorado.edu>
24 24 #
25 25 #
26 26 #*****************************************************************************
27 27
28 28 import bdb
29 29 import functools
30 30 import inspect
31 31 import sys
32 32 import warnings
33 33
34 34 from IPython import get_ipython
35 35 from IPython.utils import PyColorize, ulinecache
36 36 from IPython.utils import coloransi, py3compat
37 37 from IPython.core.excolors import exception_colors
38 38 from IPython.testing.skipdoctest import skip_doctest
39 39
40 40
41 41 prompt = 'ipdb> '
42 42
43 43 #We have to check this directly from sys.argv, config struct not yet available
44 44 from pdb import Pdb as OldPdb
45 45
46 46 # Allow the set_trace code to operate outside of an ipython instance, even if
47 47 # it does so with some limitations. The rest of this support is implemented in
48 48 # the Tracer constructor.
49 49
50 50 def make_arrow(pad):
51 51 """generate the leading arrow in front of traceback or debugger"""
52 52 if pad >= 2:
53 53 return '-'*(pad-2) + '> '
54 54 elif pad == 1:
55 55 return '>'
56 56 return ''
57 57
58 58
59 59 def BdbQuit_excepthook(et, ev, tb, excepthook=None):
60 60 """Exception hook which handles `BdbQuit` exceptions.
61 61
62 62 All other exceptions are processed using the `excepthook`
63 63 parameter.
64 64 """
65 65 warnings.warn("`BdbQuit_excepthook` is deprecated since version 5.1",
66 DeprecationWarning)
66 DeprecationWarning, stacklevel=2)
67 67 if et==bdb.BdbQuit:
68 68 print('Exiting Debugger.')
69 69 elif excepthook is not None:
70 70 excepthook(et, ev, tb)
71 71 else:
72 72 # Backwards compatibility. Raise deprecation warning?
73 73 BdbQuit_excepthook.excepthook_ori(et,ev,tb)
74 74
75 75
76 76 def BdbQuit_IPython_excepthook(self,et,ev,tb,tb_offset=None):
77 77 warnings.warn(
78 78 "`BdbQuit_IPython_excepthook` is deprecated since version 5.1",
79 DeprecationWarning)
79 DeprecationWarning, stacklevel=2)
80 80 print('Exiting Debugger.')
81 81
82 82
83 83 class Tracer(object):
84 84 """
85 85 DEPRECATED
86 86
87 87 Class for local debugging, similar to pdb.set_trace.
88 88
89 89 Instances of this class, when called, behave like pdb.set_trace, but
90 90 providing IPython's enhanced capabilities.
91 91
92 92 This is implemented as a class which must be initialized in your own code
93 93 and not as a standalone function because we need to detect at runtime
94 94 whether IPython is already active or not. That detection is done in the
95 95 constructor, ensuring that this code plays nicely with a running IPython,
96 96 while functioning acceptably (though with limitations) if outside of it.
97 97 """
98 98
99 99 @skip_doctest
100 100 def __init__(self, colors=None):
101 101 """
102 102 DEPRECATED
103 103
104 104 Create a local debugger instance.
105 105
106 106 Parameters
107 107 ----------
108 108
109 109 colors : str, optional
110 110 The name of the color scheme to use, it must be one of IPython's
111 111 valid color schemes. If not given, the function will default to
112 112 the current IPython scheme when running inside IPython, and to
113 113 'NoColor' otherwise.
114 114
115 115 Examples
116 116 --------
117 117 ::
118 118
119 119 from IPython.core.debugger import Tracer; debug_here = Tracer()
120 120
121 121 Later in your code::
122 122
123 123 debug_here() # -> will open up the debugger at that point.
124 124
125 125 Once the debugger activates, you can use all of its regular commands to
126 126 step through code, set breakpoints, etc. See the pdb documentation
127 127 from the Python standard library for usage details.
128 128 """
129 129 warnings.warn("`Tracer` is deprecated since version 5.1, directly use "
130 130 "`IPython.core.debugger.Pdb.set_trace()`",
131 DeprecationWarning)
131 DeprecationWarning, stacklevel=2)
132 132
133 133 ip = get_ipython()
134 134 if ip is None:
135 135 # Outside of ipython, we set our own exception hook manually
136 136 sys.excepthook = functools.partial(BdbQuit_excepthook,
137 137 excepthook=sys.excepthook)
138 138 def_colors = 'NoColor'
139 139 else:
140 140 # In ipython, we use its custom exception handler mechanism
141 141 def_colors = ip.colors
142 142 ip.set_custom_exc((bdb.BdbQuit,), BdbQuit_IPython_excepthook)
143 143
144 144 if colors is None:
145 145 colors = def_colors
146 146
147 147 # The stdlib debugger internally uses a modified repr from the `repr`
148 148 # module, that limits the length of printed strings to a hardcoded
149 149 # limit of 30 characters. That much trimming is too aggressive, let's
150 150 # at least raise that limit to 80 chars, which should be enough for
151 151 # most interactive uses.
152 152 try:
153 153 try:
154 154 from reprlib import aRepr # Py 3
155 155 except ImportError:
156 156 from repr import aRepr # Py 2
157 157 aRepr.maxstring = 80
158 158 except:
159 159 # This is only a user-facing convenience, so any error we encounter
160 160 # here can be warned about but can be otherwise ignored. These
161 161 # printouts will tell us about problems if this API changes
162 162 import traceback
163 163 traceback.print_exc()
164 164
165 165 self.debugger = Pdb(colors)
166 166
167 167 def __call__(self):
168 168 """Starts an interactive debugger at the point where called.
169 169
170 170 This is similar to the pdb.set_trace() function from the std lib, but
171 171 using IPython's enhanced debugger."""
172 172
173 173 self.debugger.set_trace(sys._getframe().f_back)
174 174
175 175
176 176 def decorate_fn_with_doc(new_fn, old_fn, additional_text=""):
177 177 """Make new_fn have old_fn's doc string. This is particularly useful
178 178 for the ``do_...`` commands that hook into the help system.
179 179 Adapted from from a comp.lang.python posting
180 180 by Duncan Booth."""
181 181 def wrapper(*args, **kw):
182 182 return new_fn(*args, **kw)
183 183 if old_fn.__doc__:
184 184 wrapper.__doc__ = old_fn.__doc__ + additional_text
185 185 return wrapper
186 186
187 187
188 188 def _file_lines(fname):
189 189 """Return the contents of a named file as a list of lines.
190 190
191 191 This function never raises an IOError exception: if the file can't be
192 192 read, it simply returns an empty list."""
193 193
194 194 try:
195 195 outfile = open(fname)
196 196 except IOError:
197 197 return []
198 198 else:
199 199 out = outfile.readlines()
200 200 outfile.close()
201 201 return out
202 202
203 203
204 204 class Pdb(OldPdb, object):
205 205 """Modified Pdb class, does not load readline.
206 206
207 207 for a standalone version that uses prompt_toolkit, see
208 208 `IPython.terminal.debugger.TerminalPdb` and
209 209 `IPython.terminal.debugger.set_trace()`
210 210 """
211 211
212 212 def __init__(self, color_scheme=None, completekey=None,
213 213 stdin=None, stdout=None, context=5):
214 214
215 215 # Parent constructor:
216 216 try:
217 217 self.context = int(context)
218 218 if self.context <= 0:
219 219 raise ValueError("Context must be a positive integer")
220 220 except (TypeError, ValueError):
221 221 raise ValueError("Context must be a positive integer")
222 222
223 223 OldPdb.__init__(self, completekey, stdin, stdout)
224 224
225 225 # IPython changes...
226 226 self.shell = get_ipython()
227 227
228 228 if self.shell is None:
229 229 save_main = sys.modules['__main__']
230 230 # No IPython instance running, we must create one
231 231 from IPython.terminal.interactiveshell import \
232 232 TerminalInteractiveShell
233 233 self.shell = TerminalInteractiveShell.instance()
234 234 # needed by any code which calls __import__("__main__") after
235 235 # the debugger was entered. See also #9941.
236 236 sys.modules['__main__'] = save_main
237 237
238 238 if color_scheme is not None:
239 239 warnings.warn(
240 240 "The `color_scheme` argument is deprecated since version 5.1",
241 241 DeprecationWarning, stacklevel=2)
242 242 else:
243 243 color_scheme = self.shell.colors
244 244
245 245 self.aliases = {}
246 246
247 247 # Create color table: we copy the default one from the traceback
248 248 # module and add a few attributes needed for debugging
249 249 self.color_scheme_table = exception_colors()
250 250
251 251 # shorthands
252 252 C = coloransi.TermColors
253 253 cst = self.color_scheme_table
254 254
255 255 cst['NoColor'].colors.prompt = C.NoColor
256 256 cst['NoColor'].colors.breakpoint_enabled = C.NoColor
257 257 cst['NoColor'].colors.breakpoint_disabled = C.NoColor
258 258
259 259 cst['Linux'].colors.prompt = C.Green
260 260 cst['Linux'].colors.breakpoint_enabled = C.LightRed
261 261 cst['Linux'].colors.breakpoint_disabled = C.Red
262 262
263 263 cst['LightBG'].colors.prompt = C.Blue
264 264 cst['LightBG'].colors.breakpoint_enabled = C.LightRed
265 265 cst['LightBG'].colors.breakpoint_disabled = C.Red
266 266
267 267 cst['Neutral'].colors.prompt = C.Blue
268 268 cst['Neutral'].colors.breakpoint_enabled = C.LightRed
269 269 cst['Neutral'].colors.breakpoint_disabled = C.Red
270 270
271 271
272 272 # Add a python parser so we can syntax highlight source while
273 273 # debugging.
274 274 self.parser = PyColorize.Parser(style=color_scheme)
275 275 self.set_colors(color_scheme)
276 276
277 277 # Set the prompt - the default prompt is '(Pdb)'
278 278 self.prompt = prompt
279 279
280 280 def set_colors(self, scheme):
281 281 """Shorthand access to the color table scheme selector method."""
282 282 self.color_scheme_table.set_active_scheme(scheme)
283 283 self.parser.style = scheme
284 284
285 285 def interaction(self, frame, traceback):
286 286 try:
287 287 OldPdb.interaction(self, frame, traceback)
288 288 except KeyboardInterrupt:
289 289 sys.stdout.write('\n' + self.shell.get_exception_only())
290 290
291 291 def parseline(self, line):
292 292 if line.startswith("!!"):
293 293 # Force standard behavior.
294 294 return super(Pdb, self).parseline(line[2:])
295 295 # "Smart command mode" from pdb++: don't execute commands if a variable
296 296 # with the same name exists.
297 297 cmd, arg, newline = super(Pdb, self).parseline(line)
298 298 # Fix for #9611: Do not trigger smart command if the command is `exit`
299 299 # or `quit` and it would resolve to their *global* value (the
300 300 # `ExitAutocall` object). Just checking that it is not present in the
301 301 # locals dict is not enough as locals and globals match at the
302 302 # toplevel.
303 303 if ((cmd in self.curframe.f_locals or cmd in self.curframe.f_globals)
304 304 and not (cmd in ["exit", "quit"]
305 305 and (self.curframe.f_locals is self.curframe.f_globals
306 306 or cmd not in self.curframe.f_locals))):
307 307 return super(Pdb, self).parseline("!" + line)
308 308 return super(Pdb, self).parseline(line)
309 309
310 310 def new_do_up(self, arg):
311 311 OldPdb.do_up(self, arg)
312 312 do_u = do_up = decorate_fn_with_doc(new_do_up, OldPdb.do_up)
313 313
314 314 def new_do_down(self, arg):
315 315 OldPdb.do_down(self, arg)
316 316
317 317 do_d = do_down = decorate_fn_with_doc(new_do_down, OldPdb.do_down)
318 318
319 319 def new_do_frame(self, arg):
320 320 OldPdb.do_frame(self, arg)
321 321
322 322 def new_do_quit(self, arg):
323 323
324 324 if hasattr(self, 'old_all_completions'):
325 325 self.shell.Completer.all_completions=self.old_all_completions
326 326
327 327 return OldPdb.do_quit(self, arg)
328 328
329 329 do_q = do_quit = decorate_fn_with_doc(new_do_quit, OldPdb.do_quit)
330 330
331 331 def new_do_restart(self, arg):
332 332 """Restart command. In the context of ipython this is exactly the same
333 333 thing as 'quit'."""
334 334 self.msg("Restart doesn't make sense here. Using 'quit' instead.")
335 335 return self.do_quit(arg)
336 336
337 337 def print_stack_trace(self, context=None):
338 338 if context is None:
339 339 context = self.context
340 340 try:
341 341 context=int(context)
342 342 if context <= 0:
343 343 raise ValueError("Context must be a positive integer")
344 344 except (TypeError, ValueError):
345 345 raise ValueError("Context must be a positive integer")
346 346 try:
347 347 for frame_lineno in self.stack:
348 348 self.print_stack_entry(frame_lineno, context=context)
349 349 except KeyboardInterrupt:
350 350 pass
351 351
352 352 def print_stack_entry(self,frame_lineno, prompt_prefix='\n-> ',
353 353 context=None):
354 354 if context is None:
355 355 context = self.context
356 356 try:
357 357 context=int(context)
358 358 if context <= 0:
359 359 raise ValueError("Context must be a positive integer")
360 360 except (TypeError, ValueError):
361 361 raise ValueError("Context must be a positive integer")
362 362 print(self.format_stack_entry(frame_lineno, '', context))
363 363
364 364 # vds: >>
365 365 frame, lineno = frame_lineno
366 366 filename = frame.f_code.co_filename
367 367 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
368 368 # vds: <<
369 369
370 370 def format_stack_entry(self, frame_lineno, lprefix=': ', context=None):
371 371 if context is None:
372 372 context = self.context
373 373 try:
374 374 context=int(context)
375 375 if context <= 0:
376 376 print("Context must be a positive integer")
377 377 except (TypeError, ValueError):
378 378 print("Context must be a positive integer")
379 379 try:
380 380 import reprlib # Py 3
381 381 except ImportError:
382 382 import repr as reprlib # Py 2
383 383
384 384 ret = []
385 385
386 386 Colors = self.color_scheme_table.active_colors
387 387 ColorsNormal = Colors.Normal
388 388 tpl_link = u'%s%%s%s' % (Colors.filenameEm, ColorsNormal)
389 389 tpl_call = u'%s%%s%s%%s%s' % (Colors.vName, Colors.valEm, ColorsNormal)
390 390 tpl_line = u'%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal)
391 391 tpl_line_em = u'%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line,
392 392 ColorsNormal)
393 393
394 394 frame, lineno = frame_lineno
395 395
396 396 return_value = ''
397 397 if '__return__' in frame.f_locals:
398 398 rv = frame.f_locals['__return__']
399 399 #return_value += '->'
400 400 return_value += reprlib.repr(rv) + '\n'
401 401 ret.append(return_value)
402 402
403 403 #s = filename + '(' + `lineno` + ')'
404 404 filename = self.canonic(frame.f_code.co_filename)
405 405 link = tpl_link % py3compat.cast_unicode(filename)
406 406
407 407 if frame.f_code.co_name:
408 408 func = frame.f_code.co_name
409 409 else:
410 410 func = "<lambda>"
411 411
412 412 call = ''
413 413 if func != '?':
414 414 if '__args__' in frame.f_locals:
415 415 args = reprlib.repr(frame.f_locals['__args__'])
416 416 else:
417 417 args = '()'
418 418 call = tpl_call % (func, args)
419 419
420 420 # The level info should be generated in the same format pdb uses, to
421 421 # avoid breaking the pdbtrack functionality of python-mode in *emacs.
422 422 if frame is self.curframe:
423 423 ret.append('> ')
424 424 else:
425 425 ret.append(' ')
426 426 ret.append(u'%s(%s)%s\n' % (link,lineno,call))
427 427
428 428 start = lineno - 1 - context//2
429 429 lines = ulinecache.getlines(filename)
430 430 start = min(start, len(lines) - context)
431 431 start = max(start, 0)
432 432 lines = lines[start : start + context]
433 433
434 434 for i,line in enumerate(lines):
435 435 show_arrow = (start + 1 + i == lineno)
436 436 linetpl = (frame is self.curframe or show_arrow) \
437 437 and tpl_line_em \
438 438 or tpl_line
439 439 ret.append(self.__format_line(linetpl, filename,
440 440 start + 1 + i, line,
441 441 arrow = show_arrow) )
442 442 return ''.join(ret)
443 443
444 444 def __format_line(self, tpl_line, filename, lineno, line, arrow = False):
445 445 bp_mark = ""
446 446 bp_mark_color = ""
447 447
448 448 new_line, err = self.parser.format2(line, 'str')
449 449 if not err:
450 450 line = new_line
451 451
452 452 bp = None
453 453 if lineno in self.get_file_breaks(filename):
454 454 bps = self.get_breaks(filename, lineno)
455 455 bp = bps[-1]
456 456
457 457 if bp:
458 458 Colors = self.color_scheme_table.active_colors
459 459 bp_mark = str(bp.number)
460 460 bp_mark_color = Colors.breakpoint_enabled
461 461 if not bp.enabled:
462 462 bp_mark_color = Colors.breakpoint_disabled
463 463
464 464 numbers_width = 7
465 465 if arrow:
466 466 # This is the line with the error
467 467 pad = numbers_width - len(str(lineno)) - len(bp_mark)
468 468 num = '%s%s' % (make_arrow(pad), str(lineno))
469 469 else:
470 470 num = '%*s' % (numbers_width - len(bp_mark), str(lineno))
471 471
472 472 return tpl_line % (bp_mark_color + bp_mark, num, line)
473 473
474 474
475 475 def print_list_lines(self, filename, first, last):
476 476 """The printing (as opposed to the parsing part of a 'list'
477 477 command."""
478 478 try:
479 479 Colors = self.color_scheme_table.active_colors
480 480 ColorsNormal = Colors.Normal
481 481 tpl_line = '%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal)
482 482 tpl_line_em = '%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line, ColorsNormal)
483 483 src = []
484 484 if filename == "<string>" and hasattr(self, "_exec_filename"):
485 485 filename = self._exec_filename
486 486
487 487 for lineno in range(first, last+1):
488 488 line = ulinecache.getline(filename, lineno)
489 489 if not line:
490 490 break
491 491
492 492 if lineno == self.curframe.f_lineno:
493 493 line = self.__format_line(tpl_line_em, filename, lineno, line, arrow = True)
494 494 else:
495 495 line = self.__format_line(tpl_line, filename, lineno, line, arrow = False)
496 496
497 497 src.append(line)
498 498 self.lineno = lineno
499 499
500 500 print(''.join(src))
501 501
502 502 except KeyboardInterrupt:
503 503 pass
504 504
505 505 def do_list(self, arg):
506 506 self.lastcmd = 'list'
507 507 last = None
508 508 if arg:
509 509 try:
510 510 x = eval(arg, {}, {})
511 511 if type(x) == type(()):
512 512 first, last = x
513 513 first = int(first)
514 514 last = int(last)
515 515 if last < first:
516 516 # Assume it's a count
517 517 last = first + last
518 518 else:
519 519 first = max(1, int(x) - 5)
520 520 except:
521 521 print('*** Error in argument:', repr(arg))
522 522 return
523 523 elif self.lineno is None:
524 524 first = max(1, self.curframe.f_lineno - 5)
525 525 else:
526 526 first = self.lineno + 1
527 527 if last is None:
528 528 last = first + 10
529 529 self.print_list_lines(self.curframe.f_code.co_filename, first, last)
530 530
531 531 # vds: >>
532 532 lineno = first
533 533 filename = self.curframe.f_code.co_filename
534 534 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
535 535 # vds: <<
536 536
537 537 do_l = do_list
538 538
539 539 def getsourcelines(self, obj):
540 540 lines, lineno = inspect.findsource(obj)
541 541 if inspect.isframe(obj) and obj.f_globals is obj.f_locals:
542 542 # must be a module frame: do not try to cut a block out of it
543 543 return lines, 1
544 544 elif inspect.ismodule(obj):
545 545 return lines, 1
546 546 return inspect.getblock(lines[lineno:]), lineno+1
547 547
548 548 def do_longlist(self, arg):
549 549 self.lastcmd = 'longlist'
550 550 try:
551 551 lines, lineno = self.getsourcelines(self.curframe)
552 552 except OSError as err:
553 553 self.error(err)
554 554 return
555 555 last = lineno + len(lines)
556 556 self.print_list_lines(self.curframe.f_code.co_filename, lineno, last)
557 557 do_ll = do_longlist
558 558
559 559 def do_pdef(self, arg):
560 560 """Print the call signature for any callable object.
561 561
562 562 The debugger interface to %pdef"""
563 563 namespaces = [('Locals', self.curframe.f_locals),
564 564 ('Globals', self.curframe.f_globals)]
565 565 self.shell.find_line_magic('pdef')(arg, namespaces=namespaces)
566 566
567 567 def do_pdoc(self, arg):
568 568 """Print the docstring for an object.
569 569
570 570 The debugger interface to %pdoc."""
571 571 namespaces = [('Locals', self.curframe.f_locals),
572 572 ('Globals', self.curframe.f_globals)]
573 573 self.shell.find_line_magic('pdoc')(arg, namespaces=namespaces)
574 574
575 575 def do_pfile(self, arg):
576 576 """Print (or run through pager) the file where an object is defined.
577 577
578 578 The debugger interface to %pfile.
579 579 """
580 580 namespaces = [('Locals', self.curframe.f_locals),
581 581 ('Globals', self.curframe.f_globals)]
582 582 self.shell.find_line_magic('pfile')(arg, namespaces=namespaces)
583 583
584 584 def do_pinfo(self, arg):
585 585 """Provide detailed information about an object.
586 586
587 587 The debugger interface to %pinfo, i.e., obj?."""
588 588 namespaces = [('Locals', self.curframe.f_locals),
589 589 ('Globals', self.curframe.f_globals)]
590 590 self.shell.find_line_magic('pinfo')(arg, namespaces=namespaces)
591 591
592 592 def do_pinfo2(self, arg):
593 593 """Provide extra detailed information about an object.
594 594
595 595 The debugger interface to %pinfo2, i.e., obj??."""
596 596 namespaces = [('Locals', self.curframe.f_locals),
597 597 ('Globals', self.curframe.f_globals)]
598 598 self.shell.find_line_magic('pinfo2')(arg, namespaces=namespaces)
599 599
600 600 def do_psource(self, arg):
601 601 """Print (or run through pager) the source code for an object."""
602 602 namespaces = [('Locals', self.curframe.f_locals),
603 603 ('Globals', self.curframe.f_globals)]
604 604 self.shell.find_line_magic('psource')(arg, namespaces=namespaces)
605 605
606 606 def do_where(self, arg):
607 607 """w(here)
608 608 Print a stack trace, with the most recent frame at the bottom.
609 609 An arrow indicates the "current frame", which determines the
610 610 context of most commands. 'bt' is an alias for this command.
611 611
612 612 Take a number as argument as an (optional) number of context line to
613 613 print"""
614 614 if arg:
615 615 context = int(arg)
616 616 self.print_stack_trace(context)
617 617 else:
618 618 self.print_stack_trace()
619 619
620 620 do_w = do_where
621 621
622 622
623 623 def set_trace(frame=None):
624 624 """
625 625 Start debugging from `frame`.
626 626
627 627 If frame is not specified, debugging starts from caller's frame.
628 628 """
629 629 Pdb().set_trace(frame or sys._getframe().f_back)
General Comments 0
You need to be logged in to leave comments. Login now