##// END OF EJS Templates
add __repr__ to MagicsDisplay
Michael Bianco -
Show More
@@ -1,663 +1,666 b''
1 1 """Implementation of basic magic functions."""
2 2
3 3
4 4 from logging import error
5 5 import io
6 6 import os
7 7 from pprint import pformat
8 8 import sys
9 9 from warnings import warn
10 10
11 11 from traitlets.utils.importstring import import_item
12 12 from IPython.core import magic_arguments, page
13 13 from IPython.core.error import UsageError
14 14 from IPython.core.magic import Magics, magics_class, line_magic, magic_escapes
15 15 from IPython.utils.text import format_screen, dedent, indent
16 16 from IPython.testing.skipdoctest import skip_doctest
17 17 from IPython.utils.ipstruct import Struct
18 18
19 19
20 20 class MagicsDisplay(object):
21 21 def __init__(self, magics_manager, ignore=None):
22 22 self.ignore = ignore if ignore else []
23 23 self.magics_manager = magics_manager
24 24
25 25 def _lsmagic(self):
26 26 """The main implementation of the %lsmagic"""
27 27 mesc = magic_escapes['line']
28 28 cesc = magic_escapes['cell']
29 29 mman = self.magics_manager
30 30 magics = mman.lsmagic()
31 31 out = ['Available line magics:',
32 32 mesc + (' '+mesc).join(sorted([m for m,v in magics['line'].items() if (v not in self.ignore)])),
33 33 '',
34 34 'Available cell magics:',
35 35 cesc + (' '+cesc).join(sorted([m for m,v in magics['cell'].items() if (v not in self.ignore)])),
36 36 '',
37 37 mman.auto_status()]
38 38 return '\n'.join(out)
39 39
40 40 def _repr_pretty_(self, p, cycle):
41 41 p.text(self._lsmagic())
42 42
43 def __repr__(self):
44 return self.__str__()
45
43 46 def __str__(self):
44 47 return self._lsmagic()
45 48
46 49 def _jsonable(self):
47 50 """turn magics dict into jsonable dict of the same structure
48 51
49 52 replaces object instances with their class names as strings
50 53 """
51 54 magic_dict = {}
52 55 mman = self.magics_manager
53 56 magics = mman.lsmagic()
54 57 for key, subdict in magics.items():
55 58 d = {}
56 59 magic_dict[key] = d
57 60 for name, obj in subdict.items():
58 61 try:
59 62 classname = obj.__self__.__class__.__name__
60 63 except AttributeError:
61 64 classname = 'Other'
62 65
63 66 d[name] = classname
64 67 return magic_dict
65 68
66 69 def _repr_json_(self):
67 70 return self._jsonable()
68 71
69 72
70 73 @magics_class
71 74 class BasicMagics(Magics):
72 75 """Magics that provide central IPython functionality.
73 76
74 77 These are various magics that don't fit into specific categories but that
75 78 are all part of the base 'IPython experience'."""
76 79
77 80 @skip_doctest
78 81 @magic_arguments.magic_arguments()
79 82 @magic_arguments.argument(
80 83 '-l', '--line', action='store_true',
81 84 help="""Create a line magic alias."""
82 85 )
83 86 @magic_arguments.argument(
84 87 '-c', '--cell', action='store_true',
85 88 help="""Create a cell magic alias."""
86 89 )
87 90 @magic_arguments.argument(
88 91 'name',
89 92 help="""Name of the magic to be created."""
90 93 )
91 94 @magic_arguments.argument(
92 95 'target',
93 96 help="""Name of the existing line or cell magic."""
94 97 )
95 98 @magic_arguments.argument(
96 99 '-p', '--params', default=None,
97 100 help="""Parameters passed to the magic function."""
98 101 )
99 102 @line_magic
100 103 def alias_magic(self, line=''):
101 104 """Create an alias for an existing line or cell magic.
102 105
103 106 Examples
104 107 --------
105 108 ::
106 109
107 110 In [1]: %alias_magic t timeit
108 111 Created `%t` as an alias for `%timeit`.
109 112 Created `%%t` as an alias for `%%timeit`.
110 113
111 114 In [2]: %t -n1 pass
112 115 107 ns Β± 43.6 ns per loop (mean Β± std. dev. of 7 runs, 1 loop each)
113 116
114 117 In [3]: %%t -n1
115 118 ...: pass
116 119 ...:
117 120 107 ns Β± 58.3 ns per loop (mean Β± std. dev. of 7 runs, 1 loop each)
118 121
119 122 In [4]: %alias_magic --cell whereami pwd
120 123 UsageError: Cell magic function `%%pwd` not found.
121 124 In [5]: %alias_magic --line whereami pwd
122 125 Created `%whereami` as an alias for `%pwd`.
123 126
124 127 In [6]: %whereami
125 128 Out[6]: '/home/testuser'
126 129
127 130 In [7]: %alias_magic h history -p "-l 30" --line
128 131 Created `%h` as an alias for `%history -l 30`.
129 132 """
130 133
131 134 args = magic_arguments.parse_argstring(self.alias_magic, line)
132 135 shell = self.shell
133 136 mman = self.shell.magics_manager
134 137 escs = ''.join(magic_escapes.values())
135 138
136 139 target = args.target.lstrip(escs)
137 140 name = args.name.lstrip(escs)
138 141
139 142 params = args.params
140 143 if (params and
141 144 ((params.startswith('"') and params.endswith('"'))
142 145 or (params.startswith("'") and params.endswith("'")))):
143 146 params = params[1:-1]
144 147
145 148 # Find the requested magics.
146 149 m_line = shell.find_magic(target, 'line')
147 150 m_cell = shell.find_magic(target, 'cell')
148 151 if args.line and m_line is None:
149 152 raise UsageError('Line magic function `%s%s` not found.' %
150 153 (magic_escapes['line'], target))
151 154 if args.cell and m_cell is None:
152 155 raise UsageError('Cell magic function `%s%s` not found.' %
153 156 (magic_escapes['cell'], target))
154 157
155 158 # If --line and --cell are not specified, default to the ones
156 159 # that are available.
157 160 if not args.line and not args.cell:
158 161 if not m_line and not m_cell:
159 162 raise UsageError(
160 163 'No line or cell magic with name `%s` found.' % target
161 164 )
162 165 args.line = bool(m_line)
163 166 args.cell = bool(m_cell)
164 167
165 168 params_str = "" if params is None else " " + params
166 169
167 170 if args.line:
168 171 mman.register_alias(name, target, 'line', params)
169 172 print('Created `%s%s` as an alias for `%s%s%s`.' % (
170 173 magic_escapes['line'], name,
171 174 magic_escapes['line'], target, params_str))
172 175
173 176 if args.cell:
174 177 mman.register_alias(name, target, 'cell', params)
175 178 print('Created `%s%s` as an alias for `%s%s%s`.' % (
176 179 magic_escapes['cell'], name,
177 180 magic_escapes['cell'], target, params_str))
178 181
179 182 @line_magic
180 183 def lsmagic(self, parameter_s=''):
181 184 """List currently available magic functions."""
182 185 return MagicsDisplay(self.shell.magics_manager, ignore=[])
183 186
184 187 def _magic_docs(self, brief=False, rest=False):
185 188 """Return docstrings from magic functions."""
186 189 mman = self.shell.magics_manager
187 190 docs = mman.lsmagic_docs(brief, missing='No documentation')
188 191
189 192 if rest:
190 193 format_string = '**%s%s**::\n\n%s\n\n'
191 194 else:
192 195 format_string = '%s%s:\n%s\n'
193 196
194 197 return ''.join(
195 198 [format_string % (magic_escapes['line'], fname,
196 199 indent(dedent(fndoc)))
197 200 for fname, fndoc in sorted(docs['line'].items())]
198 201 +
199 202 [format_string % (magic_escapes['cell'], fname,
200 203 indent(dedent(fndoc)))
201 204 for fname, fndoc in sorted(docs['cell'].items())]
202 205 )
203 206
204 207 @line_magic
205 208 def magic(self, parameter_s=''):
206 209 """Print information about the magic function system.
207 210
208 211 Supported formats: -latex, -brief, -rest
209 212 """
210 213
211 214 mode = ''
212 215 try:
213 216 mode = parameter_s.split()[0][1:]
214 217 except IndexError:
215 218 pass
216 219
217 220 brief = (mode == 'brief')
218 221 rest = (mode == 'rest')
219 222 magic_docs = self._magic_docs(brief, rest)
220 223
221 224 if mode == 'latex':
222 225 print(self.format_latex(magic_docs))
223 226 return
224 227 else:
225 228 magic_docs = format_screen(magic_docs)
226 229
227 230 out = ["""
228 231 IPython's 'magic' functions
229 232 ===========================
230 233
231 234 The magic function system provides a series of functions which allow you to
232 235 control the behavior of IPython itself, plus a lot of system-type
233 236 features. There are two kinds of magics, line-oriented and cell-oriented.
234 237
235 238 Line magics are prefixed with the % character and work much like OS
236 239 command-line calls: they get as an argument the rest of the line, where
237 240 arguments are passed without parentheses or quotes. For example, this will
238 241 time the given statement::
239 242
240 243 %timeit range(1000)
241 244
242 245 Cell magics are prefixed with a double %%, and they are functions that get as
243 246 an argument not only the rest of the line, but also the lines below it in a
244 247 separate argument. These magics are called with two arguments: the rest of the
245 248 call line and the body of the cell, consisting of the lines below the first.
246 249 For example::
247 250
248 251 %%timeit x = numpy.random.randn((100, 100))
249 252 numpy.linalg.svd(x)
250 253
251 254 will time the execution of the numpy svd routine, running the assignment of x
252 255 as part of the setup phase, which is not timed.
253 256
254 257 In a line-oriented client (the terminal or Qt console IPython), starting a new
255 258 input with %% will automatically enter cell mode, and IPython will continue
256 259 reading input until a blank line is given. In the notebook, simply type the
257 260 whole cell as one entity, but keep in mind that the %% escape can only be at
258 261 the very start of the cell.
259 262
260 263 NOTE: If you have 'automagic' enabled (via the command line option or with the
261 264 %automagic function), you don't need to type in the % explicitly for line
262 265 magics; cell magics always require an explicit '%%' escape. By default,
263 266 IPython ships with automagic on, so you should only rarely need the % escape.
264 267
265 268 Example: typing '%cd mydir' (without the quotes) changes your working directory
266 269 to 'mydir', if it exists.
267 270
268 271 For a list of the available magic functions, use %lsmagic. For a description
269 272 of any of them, type %magic_name?, e.g. '%cd?'.
270 273
271 274 Currently the magic system has the following functions:""",
272 275 magic_docs,
273 276 "Summary of magic functions (from %slsmagic):" % magic_escapes['line'],
274 277 str(self.lsmagic()),
275 278 ]
276 279 page.page('\n'.join(out))
277 280
278 281
279 282 @line_magic
280 283 def page(self, parameter_s=''):
281 284 """Pretty print the object and display it through a pager.
282 285
283 286 %page [options] OBJECT
284 287
285 288 If no object is given, use _ (last output).
286 289
287 290 Options:
288 291
289 292 -r: page str(object), don't pretty-print it."""
290 293
291 294 # After a function contributed by Olivier Aubert, slightly modified.
292 295
293 296 # Process options/args
294 297 opts, args = self.parse_options(parameter_s, 'r')
295 298 raw = 'r' in opts
296 299
297 300 oname = args and args or '_'
298 301 info = self.shell._ofind(oname)
299 302 if info.found:
300 303 if raw:
301 304 txt = str(info.obj)
302 305 else:
303 306 txt = pformat(info.obj)
304 307 page.page(txt)
305 308 else:
306 309 print('Object `%s` not found' % oname)
307 310
308 311 @line_magic
309 312 def pprint(self, parameter_s=''):
310 313 """Toggle pretty printing on/off."""
311 314 ptformatter = self.shell.display_formatter.formatters['text/plain']
312 315 ptformatter.pprint = bool(1 - ptformatter.pprint)
313 316 print('Pretty printing has been turned',
314 317 ['OFF','ON'][ptformatter.pprint])
315 318
316 319 @line_magic
317 320 def colors(self, parameter_s=''):
318 321 """Switch color scheme for prompts, info system and exception handlers.
319 322
320 323 Currently implemented schemes: NoColor, Linux, LightBG.
321 324
322 325 Color scheme names are not case-sensitive.
323 326
324 327 Examples
325 328 --------
326 329 To get a plain black and white terminal::
327 330
328 331 %colors nocolor
329 332 """
330 333 def color_switch_err(name):
331 334 warn('Error changing %s color schemes.\n%s' %
332 335 (name, sys.exc_info()[1]), stacklevel=2)
333 336
334 337
335 338 new_scheme = parameter_s.strip()
336 339 if not new_scheme:
337 340 raise UsageError(
338 341 "%colors: you must specify a color scheme. See '%colors?'")
339 342 # local shortcut
340 343 shell = self.shell
341 344
342 345 # Set shell colour scheme
343 346 try:
344 347 shell.colors = new_scheme
345 348 shell.refresh_style()
346 349 except:
347 350 color_switch_err('shell')
348 351
349 352 # Set exception colors
350 353 try:
351 354 shell.InteractiveTB.set_colors(scheme = new_scheme)
352 355 shell.SyntaxTB.set_colors(scheme = new_scheme)
353 356 except:
354 357 color_switch_err('exception')
355 358
356 359 # Set info (for 'object?') colors
357 360 if shell.color_info:
358 361 try:
359 362 shell.inspector.set_active_scheme(new_scheme)
360 363 except:
361 364 color_switch_err('object inspector')
362 365 else:
363 366 shell.inspector.set_active_scheme('NoColor')
364 367
365 368 @line_magic
366 369 def xmode(self, parameter_s=''):
367 370 """Switch modes for the exception handlers.
368 371
369 372 Valid modes: Plain, Context, Verbose, and Minimal.
370 373
371 374 If called without arguments, acts as a toggle.
372 375
373 376 When in verbose mode the value `--show` (and `--hide`)
374 377 will respectively show (or hide) frames with ``__tracebackhide__ =
375 378 True`` value set.
376 379 """
377 380
378 381 def xmode_switch_err(name):
379 382 warn('Error changing %s exception modes.\n%s' %
380 383 (name,sys.exc_info()[1]))
381 384
382 385 shell = self.shell
383 386 if parameter_s.strip() == "--show":
384 387 shell.InteractiveTB.skip_hidden = False
385 388 return
386 389 if parameter_s.strip() == "--hide":
387 390 shell.InteractiveTB.skip_hidden = True
388 391 return
389 392
390 393 new_mode = parameter_s.strip().capitalize()
391 394 try:
392 395 shell.InteractiveTB.set_mode(mode=new_mode)
393 396 print('Exception reporting mode:',shell.InteractiveTB.mode)
394 397 except:
395 398 xmode_switch_err('user')
396 399
397 400 @line_magic
398 401 def quickref(self, arg):
399 402 """ Show a quick reference sheet """
400 403 from IPython.core.usage import quick_reference
401 404 qr = quick_reference + self._magic_docs(brief=True)
402 405 page.page(qr)
403 406
404 407 @line_magic
405 408 def doctest_mode(self, parameter_s=''):
406 409 """Toggle doctest mode on and off.
407 410
408 411 This mode is intended to make IPython behave as much as possible like a
409 412 plain Python shell, from the perspective of how its prompts, exceptions
410 413 and output look. This makes it easy to copy and paste parts of a
411 414 session into doctests. It does so by:
412 415
413 416 - Changing the prompts to the classic ``>>>`` ones.
414 417 - Changing the exception reporting mode to 'Plain'.
415 418 - Disabling pretty-printing of output.
416 419
417 420 Note that IPython also supports the pasting of code snippets that have
418 421 leading '>>>' and '...' prompts in them. This means that you can paste
419 422 doctests from files or docstrings (even if they have leading
420 423 whitespace), and the code will execute correctly. You can then use
421 424 '%history -t' to see the translated history; this will give you the
422 425 input after removal of all the leading prompts and whitespace, which
423 426 can be pasted back into an editor.
424 427
425 428 With these features, you can switch into this mode easily whenever you
426 429 need to do testing and changes to doctests, without having to leave
427 430 your existing IPython session.
428 431 """
429 432
430 433 # Shorthands
431 434 shell = self.shell
432 435 meta = shell.meta
433 436 disp_formatter = self.shell.display_formatter
434 437 ptformatter = disp_formatter.formatters['text/plain']
435 438 # dstore is a data store kept in the instance metadata bag to track any
436 439 # changes we make, so we can undo them later.
437 440 dstore = meta.setdefault('doctest_mode',Struct())
438 441 save_dstore = dstore.setdefault
439 442
440 443 # save a few values we'll need to recover later
441 444 mode = save_dstore('mode',False)
442 445 save_dstore('rc_pprint',ptformatter.pprint)
443 446 save_dstore('xmode',shell.InteractiveTB.mode)
444 447 save_dstore('rc_separate_out',shell.separate_out)
445 448 save_dstore('rc_separate_out2',shell.separate_out2)
446 449 save_dstore('rc_separate_in',shell.separate_in)
447 450 save_dstore('rc_active_types',disp_formatter.active_types)
448 451
449 452 if not mode:
450 453 # turn on
451 454
452 455 # Prompt separators like plain python
453 456 shell.separate_in = ''
454 457 shell.separate_out = ''
455 458 shell.separate_out2 = ''
456 459
457 460
458 461 ptformatter.pprint = False
459 462 disp_formatter.active_types = ['text/plain']
460 463
461 464 shell.magic('xmode Plain')
462 465 else:
463 466 # turn off
464 467 shell.separate_in = dstore.rc_separate_in
465 468
466 469 shell.separate_out = dstore.rc_separate_out
467 470 shell.separate_out2 = dstore.rc_separate_out2
468 471
469 472 ptformatter.pprint = dstore.rc_pprint
470 473 disp_formatter.active_types = dstore.rc_active_types
471 474
472 475 shell.magic('xmode ' + dstore.xmode)
473 476
474 477 # mode here is the state before we switch; switch_doctest_mode takes
475 478 # the mode we're switching to.
476 479 shell.switch_doctest_mode(not mode)
477 480
478 481 # Store new mode and inform
479 482 dstore.mode = bool(not mode)
480 483 mode_label = ['OFF','ON'][dstore.mode]
481 484 print('Doctest mode is:', mode_label)
482 485
483 486 @line_magic
484 487 def gui(self, parameter_s=''):
485 488 """Enable or disable IPython GUI event loop integration.
486 489
487 490 %gui [GUINAME]
488 491
489 492 This magic replaces IPython's threaded shells that were activated
490 493 using the (pylab/wthread/etc.) command line flags. GUI toolkits
491 494 can now be enabled at runtime and keyboard
492 495 interrupts should work without any problems. The following toolkits
493 496 are supported: wxPython, PyQt4, PyGTK, Tk and Cocoa (OSX)::
494 497
495 498 %gui wx # enable wxPython event loop integration
496 499 %gui qt # enable PyQt/PySide event loop integration
497 500 # with the latest version available.
498 501 %gui qt6 # enable PyQt6/PySide6 event loop integration
499 502 %gui qt5 # enable PyQt5/PySide2 event loop integration
500 503 %gui gtk # enable PyGTK event loop integration
501 504 %gui gtk3 # enable Gtk3 event loop integration
502 505 %gui gtk4 # enable Gtk4 event loop integration
503 506 %gui tk # enable Tk event loop integration
504 507 %gui osx # enable Cocoa event loop integration
505 508 # (requires %matplotlib 1.1)
506 509 %gui # disable all event loop integration
507 510
508 511 WARNING: after any of these has been called you can simply create
509 512 an application object, but DO NOT start the event loop yourself, as
510 513 we have already handled that.
511 514 """
512 515 opts, arg = self.parse_options(parameter_s, '')
513 516 if arg=='': arg = None
514 517 try:
515 518 return self.shell.enable_gui(arg)
516 519 except Exception as e:
517 520 # print simple error message, rather than traceback if we can't
518 521 # hook up the GUI
519 522 error(str(e))
520 523
521 524 @skip_doctest
522 525 @line_magic
523 526 def precision(self, s=''):
524 527 """Set floating point precision for pretty printing.
525 528
526 529 Can set either integer precision or a format string.
527 530
528 531 If numpy has been imported and precision is an int,
529 532 numpy display precision will also be set, via ``numpy.set_printoptions``.
530 533
531 534 If no argument is given, defaults will be restored.
532 535
533 536 Examples
534 537 --------
535 538 ::
536 539
537 540 In [1]: from math import pi
538 541
539 542 In [2]: %precision 3
540 543 Out[2]: '%.3f'
541 544
542 545 In [3]: pi
543 546 Out[3]: 3.142
544 547
545 548 In [4]: %precision %i
546 549 Out[4]: '%i'
547 550
548 551 In [5]: pi
549 552 Out[5]: 3
550 553
551 554 In [6]: %precision %e
552 555 Out[6]: '%e'
553 556
554 557 In [7]: pi**10
555 558 Out[7]: 9.364805e+04
556 559
557 560 In [8]: %precision
558 561 Out[8]: '%r'
559 562
560 563 In [9]: pi**10
561 564 Out[9]: 93648.047476082982
562 565 """
563 566 ptformatter = self.shell.display_formatter.formatters['text/plain']
564 567 ptformatter.float_precision = s
565 568 return ptformatter.float_format
566 569
567 570 @magic_arguments.magic_arguments()
568 571 @magic_arguments.argument(
569 572 'filename', type=str,
570 573 help='Notebook name or filename'
571 574 )
572 575 @line_magic
573 576 def notebook(self, s):
574 577 """Export and convert IPython notebooks.
575 578
576 579 This function can export the current IPython history to a notebook file.
577 580 For example, to export the history to "foo.ipynb" do "%notebook foo.ipynb".
578 581 """
579 582 args = magic_arguments.parse_argstring(self.notebook, s)
580 583 outfname = os.path.expanduser(args.filename)
581 584
582 585 from nbformat import write, v4
583 586
584 587 cells = []
585 588 hist = list(self.shell.history_manager.get_range())
586 589 if(len(hist)<=1):
587 590 raise ValueError('History is empty, cannot export')
588 591 for session, execution_count, source in hist[:-1]:
589 592 cells.append(v4.new_code_cell(
590 593 execution_count=execution_count,
591 594 source=source
592 595 ))
593 596 nb = v4.new_notebook(cells=cells)
594 597 with io.open(outfname, "w", encoding="utf-8") as f:
595 598 write(nb, f, version=4)
596 599
597 600 @magics_class
598 601 class AsyncMagics(BasicMagics):
599 602
600 603 @line_magic
601 604 def autoawait(self, parameter_s):
602 605 """
603 606 Allow to change the status of the autoawait option.
604 607
605 608 This allow you to set a specific asynchronous code runner.
606 609
607 610 If no value is passed, print the currently used asynchronous integration
608 611 and whether it is activated.
609 612
610 613 It can take a number of value evaluated in the following order:
611 614
612 615 - False/false/off deactivate autoawait integration
613 616 - True/true/on activate autoawait integration using configured default
614 617 loop
615 618 - asyncio/curio/trio activate autoawait integration and use integration
616 619 with said library.
617 620
618 621 - `sync` turn on the pseudo-sync integration (mostly used for
619 622 `IPython.embed()` which does not run IPython with a real eventloop and
620 623 deactivate running asynchronous code. Turning on Asynchronous code with
621 624 the pseudo sync loop is undefined behavior and may lead IPython to crash.
622 625
623 626 If the passed parameter does not match any of the above and is a python
624 627 identifier, get said object from user namespace and set it as the
625 628 runner, and activate autoawait.
626 629
627 630 If the object is a fully qualified object name, attempt to import it and
628 631 set it as the runner, and activate autoawait.
629 632
630 633 The exact behavior of autoawait is experimental and subject to change
631 634 across version of IPython and Python.
632 635 """
633 636
634 637 param = parameter_s.strip()
635 638 d = {True: "on", False: "off"}
636 639
637 640 if not param:
638 641 print("IPython autoawait is `{}`, and set to use `{}`".format(
639 642 d[self.shell.autoawait],
640 643 self.shell.loop_runner
641 644 ))
642 645 return None
643 646
644 647 if param.lower() in ('false', 'off'):
645 648 self.shell.autoawait = False
646 649 return None
647 650 if param.lower() in ('true', 'on'):
648 651 self.shell.autoawait = True
649 652 return None
650 653
651 654 if param in self.shell.loop_runner_map:
652 655 self.shell.loop_runner, self.shell.autoawait = self.shell.loop_runner_map[param]
653 656 return None
654 657
655 658 if param in self.shell.user_ns :
656 659 self.shell.loop_runner = self.shell.user_ns[param]
657 660 self.shell.autoawait = True
658 661 return None
659 662
660 663 runner = import_item(param)
661 664
662 665 self.shell.loop_runner = runner
663 666 self.shell.autoawait = True
General Comments 0
You need to be logged in to leave comments. Login now