##// END OF EJS Templates
Cleaned up embedded shell and added cleanup method to InteractiveShell....
Brian Granger -
Show More
@@ -38,7 +38,9 b' sys.path.append(os.path.join(os.path.dirname(__file__), "extensions"))'
38 38 # Setup the top level names
39 39 #-----------------------------------------------------------------------------
40 40
41 # In some cases, these are causing circular imports.
41 42 from IPython.core.iplib import InteractiveShell
43 from IPython.core.embed import embed
42 44 from IPython.core.error import TryNext
43 45
44 46 from IPython.lib import (
@@ -215,7 +215,7 b' class IPythonCrashHandler(CrashHandler):'
215 215 rpt_add('Platform info : os.name -> %s, sys.platform -> %s' %
216 216 (os.name,sys.platform) )
217 217 rpt_add(sec_sep+'Current user configuration structure:\n\n')
218 rpt_add(pformat(self.IP.dict()))
218 # rpt_add(pformat(self.IP.dict()))
219 219 rpt_add(sec_sep+'Crash traceback:\n\n' + traceback)
220 220 try:
221 221 rpt_add(sec_sep+"History of session input:")
@@ -28,7 +28,7 b' import sys'
28 28 from IPython.core import ultratb
29 29 from IPython.core.iplib import InteractiveShell
30 30
31 from IPython.utils.traitlets import Bool, Str
31 from IPython.utils.traitlets import Bool, Str, CBool
32 32 from IPython.utils.genutils import ask_yes_no
33 33
34 34 #-----------------------------------------------------------------------------
@@ -57,11 +57,13 b' class InteractiveShellEmbed(InteractiveShell):'
57 57
58 58 dummy_mode = Bool(False)
59 59 exit_msg = Str('')
60 embedded = CBool(True)
61 embedded_active = CBool(True)
60 62
61 def __init__(self, parent=None, config=None, usage=None,
63 def __init__(self, parent=None, config=None, ipythondir=None, usage=None,
62 64 user_ns=None, user_global_ns=None,
63 banner1='', banner2='',
64 custom_exceptions=((),None), exit_msg=''):
65 banner1=None, banner2=None,
66 custom_exceptions=((),None), exit_msg=None):
65 67
66 68 # First we need to save the state of sys.displayhook and
67 69 # sys.ipcompleter so we can restore it when we are done.
@@ -69,10 +71,10 b' class InteractiveShellEmbed(InteractiveShell):'
69 71 self.save_sys_ipcompleter()
70 72
71 73 super(InteractiveShellEmbed,self).__init__(
72 parent=parent, config=config, usage=usage,
74 parent=parent, config=config, ipythondir=ipythondir, usage=usage,
73 75 user_ns=user_ns, user_global_ns=user_global_ns,
74 76 banner1=banner1, banner2=banner2,
75 custom_exceptions=custom_exceptions, embedded=True)
77 custom_exceptions=custom_exceptions)
76 78
77 79 self.save_sys_displayhook_embed()
78 80 self.exit_msg = exit_msg
@@ -87,6 +89,9 b' class InteractiveShellEmbed(InteractiveShell):'
87 89 self.restore_sys_displayhook()
88 90 self.restore_sys_ipcompleter()
89 91
92 def init_sys_modules(self):
93 pass
94
90 95 def save_sys_displayhook(self):
91 96 # sys.displayhook is a global, we need to save the user's original
92 97 # Don't rely on __displayhook__, as the user may have changed that.
@@ -121,7 +126,8 b' class InteractiveShellEmbed(InteractiveShell):'
121 126 def restore_sys_displayhook_embed(self):
122 127 sys.displayhook = self.sys_displayhook_embed
123 128
124 def __call__(self, header='', local_ns=None, global_ns=None, dummy=None):
129 def __call__(self, header='', local_ns=None, global_ns=None, dummy=None,
130 stack_depth=1):
125 131 """Activate the interactive interpreter.
126 132
127 133 __call__(self,header='',local_ns=None,global_ns,dummy=None) -> Start
@@ -166,11 +172,44 b' class InteractiveShellEmbed(InteractiveShell):'
166 172
167 173 # Call the embedding code with a stack depth of 1 so it can skip over
168 174 # our call and get the original caller's namespaces.
169 self.embed_mainloop(banner, local_ns, global_ns, stack_depth=1)
175 self.embed_mainloop(banner, local_ns, global_ns,
176 stack_depth=stack_depth)
170 177
171 if self.exit_msg:
178 if self.exit_msg is not None:
172 179 print self.exit_msg
173 180
174 181 # Restore global systems (display, completion)
175 182 self.restore_sys_displayhook()
176 183 self.restore_sys_ipcompleter()
184
185
186 _embedded_shell = None
187
188
189 def embed(header='', config=None, usage=None, banner1=None, banner2=None,
190 exit_msg=''):
191 """Call this to embed IPython at the current point in your program.
192
193 The first invocation of this will create an :class:`InteractiveShellEmbed`
194 instance and then call it. Consecutive calls just call the already
195 created instance.
196
197 Here is a simple example::
198
199 from IPython import embed
200 a = 10
201 b = 20
202 embed('First time')
203 c = 30
204 d = 40
205 embed
206
207 Full customization can be done by passing a :class:`Struct` in as the
208 config argument.
209 """
210 global _embedded_shell
211 if _embedded_shell is None:
212 _embedded_shell = InteractiveShellEmbed(config=config,
213 usage=usage, banner1=banner1, banner2=banner2, exit_msg=exit_msg)
214 _embedded_shell(header=header, stack_depth=2)
215
@@ -50,13 +50,12 b' from IPython.core.error import TryNext'
50 50
51 51 # List here all the default hooks. For now it's just the editor functions
52 52 # but over time we'll move here all the public API for user-accessible things.
53 # vds: >>
53
54 54 __all__ = ['editor', 'fix_error_editor', 'synchronize_with_editor', 'result_display',
55 55 'input_prefilter', 'shutdown_hook', 'late_startup_hook',
56 56 'generate_prompt', 'generate_output_prompt','shell_hook',
57 57 'show_in_pager','pre_prompt_hook', 'pre_runcode_hook',
58 58 'clipboard_get']
59 # vds: <<
60 59
61 60 pformat = PrettyPrinter().pformat
62 61
@@ -109,10 +108,10 b' def fix_error_editor(self,filename,linenum,column,msg):'
109 108 finally:
110 109 t.close()
111 110
112 # vds: >>
111
113 112 def synchronize_with_editor(self, filename, linenum, column):
114 113 pass
115 # vds: <<
114
116 115
117 116 class CommandChainDispatcher:
118 117 """ Dispatch calls to a chain of commands until some func can handle it
@@ -161,6 +160,7 b' class CommandChainDispatcher:'
161 160 """
162 161 return iter(self.chain)
163 162
163
164 164 def result_display(self,arg):
165 165 """ Default display hook.
166 166
@@ -183,6 +183,7 b' def result_display(self,arg):'
183 183 # the default display hook doesn't manipulate the value to put in history
184 184 return None
185 185
186
186 187 def input_prefilter(self,line):
187 188 """ Default input prefilter
188 189
@@ -197,6 +198,7 b' def input_prefilter(self,line):'
197 198 #print "attempt to rewrite",line #dbg
198 199 return line
199 200
201
200 202 def shutdown_hook(self):
201 203 """ default shutdown hook
202 204
@@ -206,31 +208,37 b' def shutdown_hook(self):'
206 208 #print "default shutdown hook ok" # dbg
207 209 return
208 210
211
209 212 def late_startup_hook(self):
210 213 """ Executed after ipython has been constructed and configured
211 214
212 215 """
213 216 #print "default startup hook ok" # dbg
214 217
218
215 219 def generate_prompt(self, is_continuation):
216 220 """ calculate and return a string with the prompt to display """
217 221 if is_continuation:
218 222 return str(self.outputcache.prompt2)
219 223 return str(self.outputcache.prompt1)
220 224
225
221 226 def generate_output_prompt(self):
222 227 return str(self.outputcache.prompt_out)
223 228
229
224 230 def shell_hook(self,cmd):
225 231 """ Run system/shell command a'la os.system() """
226 232
227 233 shell(cmd, header=self.system_header, verbose=self.system_verbose)
228 234
235
229 236 def show_in_pager(self,s):
230 237 """ Run a string through pager """
231 238 # raising TryNext here will use the default paging functionality
232 239 raise TryNext
233 240
241
234 242 def pre_prompt_hook(self):
235 243 """ Run before displaying the next prompt
236 244
@@ -240,10 +248,12 b' def pre_prompt_hook(self):'
240 248
241 249 return None
242 250
251
243 252 def pre_runcode_hook(self):
244 253 """ Executed before running the (prefiltered) code in IPython """
245 254 return None
246 255
256
247 257 def clipboard_get(self):
248 258 """ Get text from the clipboard.
249 259 """
@@ -28,8 +28,6 b' Authors:'
28 28 #-----------------------------------------------------------------------------
29 29
30 30 from IPython.core.error import TryNext, UsageError
31 from IPython.core.component import Component
32 from IPython.core.iplib import InteractiveShell
33 31
34 32 #-----------------------------------------------------------------------------
35 33 # Classes and functions
@@ -37,6 +35,7 b' from IPython.core.iplib import InteractiveShell'
37 35
38 36 def get():
39 37 """Get the most recently created InteractiveShell instance."""
38 from IPython.core.iplib import InteractiveShell
40 39 insts = InteractiveShell.get_instances()
41 40 most_recent = insts[0]
42 41 for inst in insts[1:]:
@@ -112,7 +112,9 b' class SpaceInInput(exceptions.Exception): pass'
112 112
113 113 class Bunch: pass
114 114
115 class Undefined: pass
115 class BuiltinUndefined: pass
116 BuiltinUndefined = BuiltinUndefined()
117
116 118
117 119 class Quitter(object):
118 120 """Simple class to handle exit, similar to Python 2.5's.
@@ -131,6 +133,7 b' class Quitter(object):'
131 133 def __call__(self):
132 134 self.shell.exit()
133 135
136
134 137 class InputList(list):
135 138 """Class to store user input.
136 139
@@ -146,6 +149,7 b' class InputList(list):'
146 149 def __getslice__(self,i,j):
147 150 return ''.join(list.__getslice__(self,i,j))
148 151
152
149 153 class SyntaxTB(ultratb.ListTB):
150 154 """Extension which holds some state: the last exception value"""
151 155
@@ -163,6 +167,7 b' class SyntaxTB(ultratb.ListTB):'
163 167 self.last_syntax_error = None
164 168 return e
165 169
170
166 171 def get_default_editor():
167 172 try:
168 173 ed = os.environ['EDITOR']
@@ -190,26 +195,9 b' class SeparateStr(Str):'
190 195 # Main IPython class
191 196 #-----------------------------------------------------------------------------
192 197
193 # FIXME: the Magic class is a mixin for now, and will unfortunately remain so
194 # until a full rewrite is made. I've cleaned all cross-class uses of
195 # attributes and methods, but too much user code out there relies on the
196 # equlity %foo == __IP.magic_foo, so I can't actually remove the mixin usage.
197 #
198 # But at least now, all the pieces have been separated and we could, in
199 # principle, stop using the mixin. This will ease the transition to the
200 # chainsaw branch.
201
202 # For reference, the following is the list of 'self.foo' uses in the Magic
203 # class as of 2005-12-28. These are names we CAN'T use in the main ipython
204 # class, to prevent clashes.
205
206 # ['self.__class__', 'self.__dict__', 'self._inspect', 'self._ofind',
207 # 'self.arg_err', 'self.extract_input', 'self.format_', 'self.lsmagic',
208 # 'self.magic_', 'self.options_table', 'self.parse', 'self.shell',
209 # 'self.value']
210 198
211 199 class InteractiveShell(Component, Magic):
212 """An enhanced console for Python."""
200 """An enhanced, interactive shell for Python."""
213 201
214 202 autocall = Enum((0,1,2), config_key='AUTOCALL')
215 203 autoedit_syntax = CBool(False, config_key='AUTOEDIT_SYNTAX')
@@ -229,6 +217,7 b' class InteractiveShell(Component, Magic):'
229 217 debug = CBool(False, config_key='DEBUG')
230 218 deep_reload = CBool(False, config_key='DEEP_RELOAD')
231 219 embedded = CBool(False)
220 embedded_active = CBool(False)
232 221 editor = Str(get_default_editor(), config_key='EDITOR')
233 222 filename = Str("<ipython console>")
234 223 interactive = CBool(False, config_key='INTERACTIVE')
@@ -295,24 +284,30 b' class InteractiveShell(Component, Magic):'
295 284 # Subclasses with thread support should override this as needed.
296 285 isthreaded = False
297 286
298 def __init__(self, parent=None, ipythondir=None, config=None, usage=None,
287 def __init__(self, parent=None, config=None, ipythondir=None, usage=None,
299 288 user_ns=None, user_global_ns=None,
300 289 banner1=None, banner2=None,
301 custom_exceptions=((),None), embedded=False):
290 custom_exceptions=((),None)):
302 291
303 292 # This is where traitlets with a config_key argument are updated
304 293 # from the values on config.
305 # Ideally, from here on out, the config should only be used when
306 # passing it to children components.
307 294 super(InteractiveShell, self).__init__(parent, config=config, name='__IP')
308 295
296 # These are relatively independent and stateless
309 297 self.init_ipythondir(ipythondir)
310 298 self.init_instance_attrs()
311 299 self.init_term_title()
312 300 self.init_usage(usage)
313 301 self.init_banner(banner1, banner2)
314 self.init_embedded(embedded)
302
303 # Create namespaces (user_ns, user_global_ns, alias_table, etc.)
315 304 self.init_create_namespaces(user_ns, user_global_ns)
305 # This has to be done after init_create_namespaces because it uses
306 # something in self.user_ns, but before init_sys_modules, which
307 # is the first thing to modify sys.
308 self.save_sys_module_state()
309 self.init_sys_modules()
310
316 311 self.init_history()
317 312 self.init_encoding()
318 313 self.init_handlers()
@@ -323,7 +318,7 b' class InteractiveShell(Component, Magic):'
323 318 self.init_hooks()
324 319 self.init_pushd_popd_magic()
325 320 self.init_traceback_handlers(custom_exceptions)
326 self.init_namespaces()
321 self.init_user_ns()
327 322 self.init_logger()
328 323 self.init_aliases()
329 324 self.init_builtins()
@@ -344,6 +339,10 b' class InteractiveShell(Component, Magic):'
344 339 self.init_pdb()
345 340 self.hooks.late_startup_hook()
346 341
342 def cleanup(self):
343 self.remove_builtins()
344 self.restore_sys_module_state()
345
347 346 #-------------------------------------------------------------------------
348 347 # Traitlet changed handlers
349 348 #-------------------------------------------------------------------------
@@ -372,12 +371,22 b' class InteractiveShell(Component, Magic):'
372 371 def init_ipythondir(self, ipythondir):
373 372 if ipythondir is not None:
374 373 self.ipythondir = ipythondir
374 self.config.IPYTHONDIR = self.ipythondir
375 375 return
376 376
377 if hasattr(self.config, 'IPYTHONDIR'):
378 self.ipythondir = self.config.IPYTHONDIR
377 379 if not hasattr(self.config, 'IPYTHONDIR'):
378 380 # cdw is always defined
379 381 self.ipythondir = os.getcwd()
380 return
382
383 # The caller must make sure that ipythondir exists. We should
384 # probably handle this using a Dir traitlet.
385 if not os.path.isdir(self.ipythondir):
386 raise IOError('IPython dir does not exist: %s' % self.ipythondir)
387
388 # All children can just read this
389 self.config.IPYTHONDIR = self.ipythondir
381 390
382 391 def init_instance_attrs(self):
383 392 self.jobs = BackgroundJobManager()
@@ -448,15 +457,6 b' class InteractiveShell(Component, Magic):'
448 457 if self.banner2:
449 458 self.banner += '\n' + self.banner2 + '\n'
450 459
451 def init_embedded(self, embedded):
452 # We need to know whether the instance is meant for embedding, since
453 # global/local namespaces need to be handled differently in that case
454 self.embedded = embedded
455 if embedded:
456 # Control variable so users can, from within the embedded instance,
457 # permanently deactivate it.
458 self.embedded_active = True
459
460 460 def init_create_namespaces(self, user_ns=None, user_global_ns=None):
461 461 # Create the namespace where the user will operate. user_ns is
462 462 # normally the only one used, and it is passed to the exec calls as
@@ -562,6 +562,7 b' class InteractiveShell(Component, Magic):'
562 562 self.alias_table, self.internal_ns,
563 563 self._main_ns_cache ]
564 564
565 def init_sys_modules(self):
565 566 # We need to insert into sys.modules something that looks like a
566 567 # module but which accesses the IPython namespace, for shelve and
567 568 # pickle to work interactively. Normally they rely on getting
@@ -577,11 +578,12 b' class InteractiveShell(Component, Magic):'
577 578 # shouldn't overtake the execution environment of the script they're
578 579 # embedded in).
579 580
580 if not self.embedded:
581 # This is overridden in the InteractiveShellEmbed subclass to a no-op.
582
581 583 try:
582 584 main_name = self.user_ns['__name__']
583 585 except KeyError:
584 raise KeyError,'user_ns dictionary MUST have a "__name__" key'
586 raise KeyError('user_ns dictionary MUST have a "__name__" key')
585 587 else:
586 588 sys.modules[main_name] = FakeModule(self.user_ns)
587 589
@@ -833,12 +835,8 b' class InteractiveShell(Component, Magic):'
833 835
834 836 def init_builtins(self):
835 837 # track which builtins we add, so we can clean up later
836 self.builtins_added = {}
837 # This method will add the necessary builtins for operation, but
838 # tracking what it did via the builtins_added dict.
839
840 #TODO: remove this, redundant. I don't understand why this is
841 # redundant?
838 self._orig_builtins = {}
839 self._builtins_added = False
842 840 self.add_builtins()
843 841
844 842 def init_shadow_hist(self):
@@ -1041,7 +1039,7 b' class InteractiveShell(Component, Magic):'
1041 1039 # self.extensions[mod] = m
1042 1040 # return m
1043 1041
1044 def init_namespaces(self):
1042 def init_user_ns(self):
1045 1043 """Initialize all user-visible namespaces to their minimum defaults.
1046 1044
1047 1045 Certain history lists are also initialized here, as they effectively
@@ -1077,26 +1075,40 b' class InteractiveShell(Component, Magic):'
1077 1075 except ImportError:
1078 1076 warn('help() not available - check site.py')
1079 1077
1078 def add_builtin(self, key, value):
1079 """Add a builtin and save the original."""
1080 orig = __builtin__.__dict__.get(key, BuiltinUndefined)
1081 self._orig_builtins[key] = orig
1082 __builtin__.__dict__[key] = value
1083
1084 def remove_builtin(self, key):
1085 """Remove an added builtin and re-set the original."""
1086 try:
1087 orig = self._orig_builtins.pop(key)
1088 except KeyError:
1089 pass
1090 else:
1091 if orig is BuiltinUndefined:
1092 del __builtin__.__dict__[key]
1093 else:
1094 __builtin__.__dict__[key] = orig
1095
1080 1096 def add_builtins(self):
1081 1097 """Store ipython references into the __builtin__ namespace.
1082 1098
1083 1099 We strive to modify the __builtin__ namespace as little as possible.
1084 1100 """
1101 if not self._builtins_added:
1102 self.add_builtin('exit', Quitter(self,'exit'))
1103 self.add_builtin('quit', Quitter(self,'quit'))
1085 1104
1086 # Install our own quitter instead of the builtins.
1087 # This used to be in the __init__ method, but this is a better
1088 # place for it. These can be incorporated to the logic below
1089 # when it is refactored.
1090 __builtin__.exit = Quitter(self,'exit')
1091 __builtin__.quit = Quitter(self,'quit')
1092
1093 # Recursive reload
1105 # Recursive reload function
1094 1106 try:
1095 1107 from IPython.lib import deepreload
1096 1108 if self.deep_reload:
1097 __builtin__.reload = deepreload.reload
1109 self.add_builtin('reload', deepreload.reload)
1098 1110 else:
1099 __builtin__.dreload = deepreload.reload
1111 self.add_builtin('dreload', deepreload.reload)
1100 1112 del deepreload
1101 1113 except ImportError:
1102 1114 pass
@@ -1106,16 +1118,49 b' class InteractiveShell(Component, Magic):'
1106 1118 # another. Each will increase its value by one upon being activated,
1107 1119 # which also gives us a way to determine the nesting level.
1108 1120 __builtin__.__dict__.setdefault('__IPYTHON__active',0)
1121 self._builtins_added = True
1109 1122
1110 def clean_builtins(self):
1123 def remove_builtins(self):
1111 1124 """Remove any builtins which might have been added by add_builtins, or
1112 1125 restore overwritten ones to their previous values."""
1113 for biname,bival in self.builtins_added.items():
1114 if bival is Undefined:
1115 del __builtin__.__dict__[biname]
1116 else:
1117 __builtin__.__dict__[biname] = bival
1118 self.builtins_added.clear()
1126 if self._builtins_added:
1127 for key in self._orig_builtins.keys():
1128 self.remove_builtin(key)
1129 self._orig_builtins.clear()
1130 self._builtins_added = False
1131
1132 def save_sys_module_state(self):
1133 """Save the state of hooks in the sys module.
1134
1135 This has to be called after self.user_ns is created.
1136 """
1137 self._orig_sys_module_state = {}
1138 self._orig_sys_module_state['stdin'] = sys.stdin
1139 self._orig_sys_module_state['stdout'] = sys.stdout
1140 self._orig_sys_module_state['stderr'] = sys.stderr
1141 self._orig_sys_module_state['displayhook'] = sys.displayhook
1142 self._orig_sys_module_state['excepthook'] = sys.excepthook
1143 try:
1144 self._orig_sys_modules_main_name = self.user_ns['__name__']
1145 except KeyError:
1146 pass
1147
1148 def restore_sys_module_state(self):
1149 """Restore the state of the sys module."""
1150 try:
1151 for k, v in self._orig_sys_module_state.items():
1152 setattr(sys, k, v)
1153 except AttributeError:
1154 pass
1155 try:
1156 delattr(sys, 'ipcompleter')
1157 except AttributeError:
1158 pass
1159 # Reset what what done in self.init_sys_modules
1160 try:
1161 sys.modules[self.user_ns['__name__']] = self._orig_sys_modules_main_name
1162 except (AttributeError, KeyError):
1163 pass
1119 1164
1120 1165 def set_hook(self,name,hook, priority = 50, str_key = None, re_key = None):
1121 1166 """set_hook(name,hook) -> sets an internal IPython hook.
@@ -1156,9 +1201,6 b' class InteractiveShell(Component, Magic):'
1156 1201
1157 1202 setattr(self.hooks,name, dp)
1158 1203
1159
1160 #setattr(self.hooks,name,new.instancemethod(hook,self,self.__class__))
1161
1162 1204 def set_crash_handler(self,crashHandler):
1163 1205 """Set the IPython crash handler.
1164 1206
@@ -1174,7 +1216,6 b' class InteractiveShell(Component, Magic):'
1174 1216 # frameworks).
1175 1217 self.sys_excepthook = sys.excepthook
1176 1218
1177
1178 1219 def set_custom_exc(self,exc_tuple,handler):
1179 1220 """set_custom_exc(exc_tuple,handler)
1180 1221
@@ -1382,14 +1423,14 b' class InteractiveShell(Component, Magic):'
1382 1423
1383 1424 def ex(self, cmd):
1384 1425 """Execute a normal python statement in user namespace."""
1385 exec cmd in self.user_ns
1426 exec cmd in self.user_global_ns, self.user_ns
1386 1427
1387 1428 def ev(self, expr):
1388 1429 """Evaluate python expression expr in user namespace.
1389 1430
1390 1431 Returns the result of evaluation
1391 1432 """
1392 return eval(expr,self.user_ns)
1433 return eval(expr, self.user_global_ns, self.user_ns)
1393 1434
1394 1435 def getoutput(self, cmd):
1395 1436 return getoutput(self.var_expand(cmd,depth=2),
@@ -1538,7 +1579,7 b' class InteractiveShell(Component, Magic):'
1538 1579 self.input_hist_raw[:] = []
1539 1580 self.output_hist.clear()
1540 1581 # Restore the user namespaces to minimal usability
1541 self.init_namespaces()
1582 self.init_user_ns()
1542 1583
1543 1584 def savehist(self):
1544 1585 """Save input history to a file (via readline library)."""
@@ -1937,7 +1978,7 b' class InteractiveShell(Component, Magic):'
1937 1978 for var in local_varnames:
1938 1979 delvar(var,None)
1939 1980 # and clean builtins we may have overridden
1940 self.clean_builtins()
1981 self.remove_builtins()
1941 1982
1942 1983 def interact_prompt(self):
1943 1984 """ Print the prompt (in read-eval-print loop)
General Comments 0
You need to be logged in to leave comments. Login now