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