##// END OF EJS Templates
add terminado as [notebook] dep on non-Windows
Min RK -
Show More
@@ -1,338 +1,341
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
2 # -*- coding: utf-8 -*-
3 """Setup script for IPython.
3 """Setup script for IPython.
4
4
5 Under Posix environments it works like a typical setup.py script.
5 Under Posix environments it works like a typical setup.py script.
6 Under Windows, the command sdist is not supported, since IPython
6 Under Windows, the command sdist is not supported, since IPython
7 requires utilities which are not available under Windows."""
7 requires utilities which are not available under Windows."""
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (c) 2008-2011, IPython Development Team.
10 # Copyright (c) 2008-2011, IPython Development Team.
11 # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
11 # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
12 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
12 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
13 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
13 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
14 #
14 #
15 # Distributed under the terms of the Modified BSD License.
15 # Distributed under the terms of the Modified BSD License.
16 #
16 #
17 # The full license is in the file COPYING.rst, distributed with this software.
17 # The full license is in the file COPYING.rst, distributed with this software.
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Minimal Python version sanity check
21 # Minimal Python version sanity check
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 from __future__ import print_function
23 from __future__ import print_function
24
24
25 import sys
25 import sys
26
26
27 # This check is also made in IPython/__init__, don't forget to update both when
27 # This check is also made in IPython/__init__, don't forget to update both when
28 # changing Python version requirements.
28 # changing Python version requirements.
29 v = sys.version_info
29 v = sys.version_info
30 if v[:2] < (2,7) or (v[0] >= 3 and v[:2] < (3,3)):
30 if v[:2] < (2,7) or (v[0] >= 3 and v[:2] < (3,3)):
31 error = "ERROR: IPython requires Python version 2.7 or 3.3 or above."
31 error = "ERROR: IPython requires Python version 2.7 or 3.3 or above."
32 print(error, file=sys.stderr)
32 print(error, file=sys.stderr)
33 sys.exit(1)
33 sys.exit(1)
34
34
35 PY3 = (sys.version_info[0] >= 3)
35 PY3 = (sys.version_info[0] >= 3)
36
36
37 # At least we're on the python version we need, move on.
37 # At least we're on the python version we need, move on.
38
38
39 #-------------------------------------------------------------------------------
39 #-------------------------------------------------------------------------------
40 # Imports
40 # Imports
41 #-------------------------------------------------------------------------------
41 #-------------------------------------------------------------------------------
42
42
43 # Stdlib imports
43 # Stdlib imports
44 import os
44 import os
45 import shutil
45 import shutil
46
46
47 from glob import glob
47 from glob import glob
48
48
49 # BEFORE importing distutils, remove MANIFEST. distutils doesn't properly
49 # BEFORE importing distutils, remove MANIFEST. distutils doesn't properly
50 # update it when the contents of directories change.
50 # update it when the contents of directories change.
51 if os.path.exists('MANIFEST'): os.remove('MANIFEST')
51 if os.path.exists('MANIFEST'): os.remove('MANIFEST')
52
52
53 from distutils.core import setup
53 from distutils.core import setup
54
54
55 # Our own imports
55 # Our own imports
56 from setupbase import target_update
56 from setupbase import target_update
57
57
58 from setupbase import (
58 from setupbase import (
59 setup_args,
59 setup_args,
60 find_packages,
60 find_packages,
61 find_package_data,
61 find_package_data,
62 check_package_data_first,
62 check_package_data_first,
63 find_entry_points,
63 find_entry_points,
64 build_scripts_entrypt,
64 build_scripts_entrypt,
65 find_data_files,
65 find_data_files,
66 check_for_dependencies,
66 check_for_dependencies,
67 git_prebuild,
67 git_prebuild,
68 check_submodule_status,
68 check_submodule_status,
69 update_submodules,
69 update_submodules,
70 require_submodules,
70 require_submodules,
71 UpdateSubmodules,
71 UpdateSubmodules,
72 get_bdist_wheel,
72 get_bdist_wheel,
73 CompileCSS,
73 CompileCSS,
74 JavascriptVersion,
74 JavascriptVersion,
75 css_js_prerelease,
75 css_js_prerelease,
76 install_symlinked,
76 install_symlinked,
77 install_lib_symlink,
77 install_lib_symlink,
78 install_scripts_for_symlink,
78 install_scripts_for_symlink,
79 unsymlink,
79 unsymlink,
80 )
80 )
81 from setupext import setupext
81 from setupext import setupext
82
82
83 isfile = os.path.isfile
83 isfile = os.path.isfile
84 pjoin = os.path.join
84 pjoin = os.path.join
85
85
86 #-------------------------------------------------------------------------------
86 #-------------------------------------------------------------------------------
87 # Handle OS specific things
87 # Handle OS specific things
88 #-------------------------------------------------------------------------------
88 #-------------------------------------------------------------------------------
89
89
90 if os.name in ('nt','dos'):
90 if os.name in ('nt','dos'):
91 os_name = 'windows'
91 os_name = 'windows'
92 else:
92 else:
93 os_name = os.name
93 os_name = os.name
94
94
95 # Under Windows, 'sdist' has not been supported. Now that the docs build with
95 # Under Windows, 'sdist' has not been supported. Now that the docs build with
96 # Sphinx it might work, but let's not turn it on until someone confirms that it
96 # Sphinx it might work, but let's not turn it on until someone confirms that it
97 # actually works.
97 # actually works.
98 if os_name == 'windows' and 'sdist' in sys.argv:
98 if os_name == 'windows' and 'sdist' in sys.argv:
99 print('The sdist command is not available under Windows. Exiting.')
99 print('The sdist command is not available under Windows. Exiting.')
100 sys.exit(1)
100 sys.exit(1)
101
101
102 #-------------------------------------------------------------------------------
102 #-------------------------------------------------------------------------------
103 # Make sure we aren't trying to run without submodules
103 # Make sure we aren't trying to run without submodules
104 #-------------------------------------------------------------------------------
104 #-------------------------------------------------------------------------------
105 here = os.path.abspath(os.path.dirname(__file__))
105 here = os.path.abspath(os.path.dirname(__file__))
106
106
107 def require_clean_submodules():
107 def require_clean_submodules():
108 """Check on git submodules before distutils can do anything
108 """Check on git submodules before distutils can do anything
109
109
110 Since distutils cannot be trusted to update the tree
110 Since distutils cannot be trusted to update the tree
111 after everything has been set in motion,
111 after everything has been set in motion,
112 this is not a distutils command.
112 this is not a distutils command.
113 """
113 """
114 # PACKAGERS: Add a return here to skip checks for git submodules
114 # PACKAGERS: Add a return here to skip checks for git submodules
115
115
116 # don't do anything if nothing is actually supposed to happen
116 # don't do anything if nothing is actually supposed to happen
117 for do_nothing in ('-h', '--help', '--help-commands', 'clean', 'submodule'):
117 for do_nothing in ('-h', '--help', '--help-commands', 'clean', 'submodule'):
118 if do_nothing in sys.argv:
118 if do_nothing in sys.argv:
119 return
119 return
120
120
121 status = check_submodule_status(here)
121 status = check_submodule_status(here)
122
122
123 if status == "missing":
123 if status == "missing":
124 print("checking out submodules for the first time")
124 print("checking out submodules for the first time")
125 update_submodules(here)
125 update_submodules(here)
126 elif status == "unclean":
126 elif status == "unclean":
127 print('\n'.join([
127 print('\n'.join([
128 "Cannot build / install IPython with unclean submodules",
128 "Cannot build / install IPython with unclean submodules",
129 "Please update submodules with",
129 "Please update submodules with",
130 " python setup.py submodule",
130 " python setup.py submodule",
131 "or",
131 "or",
132 " git submodule update",
132 " git submodule update",
133 "or commit any submodule changes you have made."
133 "or commit any submodule changes you have made."
134 ]))
134 ]))
135 sys.exit(1)
135 sys.exit(1)
136
136
137 require_clean_submodules()
137 require_clean_submodules()
138
138
139 #-------------------------------------------------------------------------------
139 #-------------------------------------------------------------------------------
140 # Things related to the IPython documentation
140 # Things related to the IPython documentation
141 #-------------------------------------------------------------------------------
141 #-------------------------------------------------------------------------------
142
142
143 # update the manuals when building a source dist
143 # update the manuals when building a source dist
144 if len(sys.argv) >= 2 and sys.argv[1] in ('sdist','bdist_rpm'):
144 if len(sys.argv) >= 2 and sys.argv[1] in ('sdist','bdist_rpm'):
145
145
146 # List of things to be updated. Each entry is a triplet of args for
146 # List of things to be updated. Each entry is a triplet of args for
147 # target_update()
147 # target_update()
148 to_update = [
148 to_update = [
149 # FIXME - Disabled for now: we need to redo an automatic way
149 # FIXME - Disabled for now: we need to redo an automatic way
150 # of generating the magic info inside the rst.
150 # of generating the magic info inside the rst.
151 #('docs/magic.tex',
151 #('docs/magic.tex',
152 #['IPython/Magic.py'],
152 #['IPython/Magic.py'],
153 #"cd doc && ./update_magic.sh" ),
153 #"cd doc && ./update_magic.sh" ),
154
154
155 ('docs/man/ipcluster.1.gz',
155 ('docs/man/ipcluster.1.gz',
156 ['docs/man/ipcluster.1'],
156 ['docs/man/ipcluster.1'],
157 'cd docs/man && gzip -9c ipcluster.1 > ipcluster.1.gz'),
157 'cd docs/man && gzip -9c ipcluster.1 > ipcluster.1.gz'),
158
158
159 ('docs/man/ipcontroller.1.gz',
159 ('docs/man/ipcontroller.1.gz',
160 ['docs/man/ipcontroller.1'],
160 ['docs/man/ipcontroller.1'],
161 'cd docs/man && gzip -9c ipcontroller.1 > ipcontroller.1.gz'),
161 'cd docs/man && gzip -9c ipcontroller.1 > ipcontroller.1.gz'),
162
162
163 ('docs/man/ipengine.1.gz',
163 ('docs/man/ipengine.1.gz',
164 ['docs/man/ipengine.1'],
164 ['docs/man/ipengine.1'],
165 'cd docs/man && gzip -9c ipengine.1 > ipengine.1.gz'),
165 'cd docs/man && gzip -9c ipengine.1 > ipengine.1.gz'),
166
166
167 ('docs/man/ipython.1.gz',
167 ('docs/man/ipython.1.gz',
168 ['docs/man/ipython.1'],
168 ['docs/man/ipython.1'],
169 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz'),
169 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz'),
170
170
171 ]
171 ]
172
172
173
173
174 [ target_update(*t) for t in to_update ]
174 [ target_update(*t) for t in to_update ]
175
175
176 #---------------------------------------------------------------------------
176 #---------------------------------------------------------------------------
177 # Find all the packages, package data, and data_files
177 # Find all the packages, package data, and data_files
178 #---------------------------------------------------------------------------
178 #---------------------------------------------------------------------------
179
179
180 packages = find_packages()
180 packages = find_packages()
181 package_data = find_package_data()
181 package_data = find_package_data()
182
182
183 data_files = find_data_files()
183 data_files = find_data_files()
184
184
185 setup_args['packages'] = packages
185 setup_args['packages'] = packages
186 setup_args['package_data'] = package_data
186 setup_args['package_data'] = package_data
187 setup_args['data_files'] = data_files
187 setup_args['data_files'] = data_files
188
188
189 #---------------------------------------------------------------------------
189 #---------------------------------------------------------------------------
190 # custom distutils commands
190 # custom distutils commands
191 #---------------------------------------------------------------------------
191 #---------------------------------------------------------------------------
192 # imports here, so they are after setuptools import if there was one
192 # imports here, so they are after setuptools import if there was one
193 from distutils.command.sdist import sdist
193 from distutils.command.sdist import sdist
194 from distutils.command.upload import upload
194 from distutils.command.upload import upload
195
195
196 class UploadWindowsInstallers(upload):
196 class UploadWindowsInstallers(upload):
197
197
198 description = "Upload Windows installers to PyPI (only used from tools/release_windows.py)"
198 description = "Upload Windows installers to PyPI (only used from tools/release_windows.py)"
199 user_options = upload.user_options + [
199 user_options = upload.user_options + [
200 ('files=', 'f', 'exe file (or glob) to upload')
200 ('files=', 'f', 'exe file (or glob) to upload')
201 ]
201 ]
202 def initialize_options(self):
202 def initialize_options(self):
203 upload.initialize_options(self)
203 upload.initialize_options(self)
204 meta = self.distribution.metadata
204 meta = self.distribution.metadata
205 base = '{name}-{version}'.format(
205 base = '{name}-{version}'.format(
206 name=meta.get_name(),
206 name=meta.get_name(),
207 version=meta.get_version()
207 version=meta.get_version()
208 )
208 )
209 self.files = os.path.join('dist', '%s.*.exe' % base)
209 self.files = os.path.join('dist', '%s.*.exe' % base)
210
210
211 def run(self):
211 def run(self):
212 for dist_file in glob(self.files):
212 for dist_file in glob(self.files):
213 self.upload_file('bdist_wininst', 'any', dist_file)
213 self.upload_file('bdist_wininst', 'any', dist_file)
214
214
215 setup_args['cmdclass'] = {
215 setup_args['cmdclass'] = {
216 'build_py': css_js_prerelease(
216 'build_py': css_js_prerelease(
217 check_package_data_first(git_prebuild('IPython'))),
217 check_package_data_first(git_prebuild('IPython'))),
218 'sdist' : css_js_prerelease(git_prebuild('IPython', sdist)),
218 'sdist' : css_js_prerelease(git_prebuild('IPython', sdist)),
219 'upload_wininst' : UploadWindowsInstallers,
219 'upload_wininst' : UploadWindowsInstallers,
220 'submodule' : UpdateSubmodules,
220 'submodule' : UpdateSubmodules,
221 'css' : CompileCSS,
221 'css' : CompileCSS,
222 'symlink': install_symlinked,
222 'symlink': install_symlinked,
223 'install_lib_symlink': install_lib_symlink,
223 'install_lib_symlink': install_lib_symlink,
224 'install_scripts_sym': install_scripts_for_symlink,
224 'install_scripts_sym': install_scripts_for_symlink,
225 'unsymlink': unsymlink,
225 'unsymlink': unsymlink,
226 'jsversion' : JavascriptVersion,
226 'jsversion' : JavascriptVersion,
227 }
227 }
228
228
229 #---------------------------------------------------------------------------
229 #---------------------------------------------------------------------------
230 # Handle scripts, dependencies, and setuptools specific things
230 # Handle scripts, dependencies, and setuptools specific things
231 #---------------------------------------------------------------------------
231 #---------------------------------------------------------------------------
232
232
233 # For some commands, use setuptools. Note that we do NOT list install here!
233 # For some commands, use setuptools. Note that we do NOT list install here!
234 # If you want a setuptools-enhanced install, just run 'setupegg.py install'
234 # If you want a setuptools-enhanced install, just run 'setupegg.py install'
235 needs_setuptools = set(('develop', 'release', 'bdist_egg', 'bdist_rpm',
235 needs_setuptools = set(('develop', 'release', 'bdist_egg', 'bdist_rpm',
236 'bdist', 'bdist_dumb', 'bdist_wininst', 'bdist_wheel',
236 'bdist', 'bdist_dumb', 'bdist_wininst', 'bdist_wheel',
237 'egg_info', 'easy_install', 'upload', 'install_egg_info',
237 'egg_info', 'easy_install', 'upload', 'install_egg_info',
238 ))
238 ))
239
239
240 if len(needs_setuptools.intersection(sys.argv)) > 0:
240 if len(needs_setuptools.intersection(sys.argv)) > 0:
241 import setuptools
241 import setuptools
242
242
243 # This dict is used for passing extra arguments that are setuptools
243 # This dict is used for passing extra arguments that are setuptools
244 # specific to setup
244 # specific to setup
245 setuptools_extra_args = {}
245 setuptools_extra_args = {}
246
246
247 # setuptools requirements
247 # setuptools requirements
248
248
249 extras_require = dict(
249 extras_require = dict(
250 parallel = ['pyzmq>=2.1.11'],
250 parallel = ['pyzmq>=2.1.11'],
251 qtconsole = ['pyzmq>=2.1.11', 'pygments'],
251 qtconsole = ['pyzmq>=2.1.11', 'pygments'],
252 zmq = ['pyzmq>=2.1.11'],
252 zmq = ['pyzmq>=2.1.11'],
253 doc = ['Sphinx>=1.1', 'numpydoc'],
253 doc = ['Sphinx>=1.1', 'numpydoc'],
254 test = ['nose>=0.10.1', 'requests'],
254 test = ['nose>=0.10.1', 'requests'],
255 terminal = [],
255 terminal = [],
256 nbformat = ['jsonschema>=2.0'],
256 nbformat = ['jsonschema>=2.0'],
257 notebook = ['tornado>=4.0', 'pyzmq>=2.1.11', 'jinja2', 'pygments', 'mistune>=0.5'],
257 notebook = ['tornado>=4.0', 'pyzmq>=2.1.11', 'jinja2', 'pygments', 'mistune>=0.5'],
258 nbconvert = ['pygments', 'jinja2', 'mistune>=0.3.1']
258 nbconvert = ['pygments', 'jinja2', 'mistune>=0.3.1']
259 )
259 )
260
260
261 if not sys.platform.startswith('win'):
262 extras_require['notebook'].append('terminado>=0.3.3')
263
261 if sys.version_info < (3, 3):
264 if sys.version_info < (3, 3):
262 extras_require['test'].append('mock')
265 extras_require['test'].append('mock')
263
266
264 extras_require['notebook'].extend(extras_require['nbformat'])
267 extras_require['notebook'].extend(extras_require['nbformat'])
265 extras_require['nbconvert'].extend(extras_require['nbformat'])
268 extras_require['nbconvert'].extend(extras_require['nbformat'])
266
269
267 install_requires = []
270 install_requires = []
268
271
269 # add readline
272 # add readline
270 if sys.platform == 'darwin':
273 if sys.platform == 'darwin':
271 if 'bdist_wheel' in sys.argv[1:] or not setupext.check_for_readline():
274 if 'bdist_wheel' in sys.argv[1:] or not setupext.check_for_readline():
272 install_requires.append('gnureadline')
275 install_requires.append('gnureadline')
273 elif sys.platform.startswith('win'):
276 elif sys.platform.startswith('win'):
274 extras_require['terminal'].append('pyreadline>=2.0')
277 extras_require['terminal'].append('pyreadline>=2.0')
275
278
276 everything = set()
279 everything = set()
277 for deps in extras_require.values():
280 for deps in extras_require.values():
278 everything.update(deps)
281 everything.update(deps)
279 extras_require['all'] = everything
282 extras_require['all'] = everything
280
283
281 if 'setuptools' in sys.modules:
284 if 'setuptools' in sys.modules:
282 # setup.py develop should check for submodules
285 # setup.py develop should check for submodules
283 from setuptools.command.develop import develop
286 from setuptools.command.develop import develop
284 setup_args['cmdclass']['develop'] = require_submodules(develop)
287 setup_args['cmdclass']['develop'] = require_submodules(develop)
285 setup_args['cmdclass']['bdist_wheel'] = css_js_prerelease(get_bdist_wheel())
288 setup_args['cmdclass']['bdist_wheel'] = css_js_prerelease(get_bdist_wheel())
286
289
287 setuptools_extra_args['zip_safe'] = False
290 setuptools_extra_args['zip_safe'] = False
288 setuptools_extra_args['entry_points'] = {
291 setuptools_extra_args['entry_points'] = {
289 'console_scripts': find_entry_points(),
292 'console_scripts': find_entry_points(),
290 'pygments.lexers': [
293 'pygments.lexers': [
291 'ipythonconsole = IPython.nbconvert.utils.lexers:IPythonConsoleLexer',
294 'ipythonconsole = IPython.nbconvert.utils.lexers:IPythonConsoleLexer',
292 'ipython = IPython.nbconvert.utils.lexers:IPythonLexer',
295 'ipython = IPython.nbconvert.utils.lexers:IPythonLexer',
293 'ipython3 = IPython.nbconvert.utils.lexers:IPython3Lexer',
296 'ipython3 = IPython.nbconvert.utils.lexers:IPython3Lexer',
294 ],
297 ],
295 }
298 }
296 setup_args['extras_require'] = extras_require
299 setup_args['extras_require'] = extras_require
297 requires = setup_args['install_requires'] = install_requires
300 requires = setup_args['install_requires'] = install_requires
298
301
299 # Script to be run by the windows binary installer after the default setup
302 # Script to be run by the windows binary installer after the default setup
300 # routine, to add shortcuts and similar windows-only things. Windows
303 # routine, to add shortcuts and similar windows-only things. Windows
301 # post-install scripts MUST reside in the scripts/ dir, otherwise distutils
304 # post-install scripts MUST reside in the scripts/ dir, otherwise distutils
302 # doesn't find them.
305 # doesn't find them.
303 if 'bdist_wininst' in sys.argv:
306 if 'bdist_wininst' in sys.argv:
304 if len(sys.argv) > 2 and \
307 if len(sys.argv) > 2 and \
305 ('sdist' in sys.argv or 'bdist_rpm' in sys.argv):
308 ('sdist' in sys.argv or 'bdist_rpm' in sys.argv):
306 print("ERROR: bdist_wininst must be run alone. Exiting.", file=sys.stderr)
309 print("ERROR: bdist_wininst must be run alone. Exiting.", file=sys.stderr)
307 sys.exit(1)
310 sys.exit(1)
308 setup_args['data_files'].append(
311 setup_args['data_files'].append(
309 ['Scripts', ('scripts/ipython.ico', 'scripts/ipython_nb.ico')])
312 ['Scripts', ('scripts/ipython.ico', 'scripts/ipython_nb.ico')])
310 setup_args['scripts'] = [pjoin('scripts','ipython_win_post_install.py')]
313 setup_args['scripts'] = [pjoin('scripts','ipython_win_post_install.py')]
311 setup_args['options'] = {"bdist_wininst":
314 setup_args['options'] = {"bdist_wininst":
312 {"install_script":
315 {"install_script":
313 "ipython_win_post_install.py"}}
316 "ipython_win_post_install.py"}}
314
317
315 else:
318 else:
316 # If we are installing without setuptools, call this function which will
319 # If we are installing without setuptools, call this function which will
317 # check for dependencies an inform the user what is needed. This is
320 # check for dependencies an inform the user what is needed. This is
318 # just to make life easy for users.
321 # just to make life easy for users.
319 for install_cmd in ('install', 'symlink'):
322 for install_cmd in ('install', 'symlink'):
320 if install_cmd in sys.argv:
323 if install_cmd in sys.argv:
321 check_for_dependencies()
324 check_for_dependencies()
322 break
325 break
323 # scripts has to be a non-empty list, or install_scripts isn't called
326 # scripts has to be a non-empty list, or install_scripts isn't called
324 setup_args['scripts'] = [e.split('=')[0].strip() for e in find_entry_points()]
327 setup_args['scripts'] = [e.split('=')[0].strip() for e in find_entry_points()]
325
328
326 setup_args['cmdclass']['build_scripts'] = build_scripts_entrypt
329 setup_args['cmdclass']['build_scripts'] = build_scripts_entrypt
327
330
328 #---------------------------------------------------------------------------
331 #---------------------------------------------------------------------------
329 # Do the actual setup now
332 # Do the actual setup now
330 #---------------------------------------------------------------------------
333 #---------------------------------------------------------------------------
331
334
332 setup_args.update(setuptools_extra_args)
335 setup_args.update(setuptools_extra_args)
333
336
334 def main():
337 def main():
335 setup(**setup_args)
338 setup(**setup_args)
336
339
337 if __name__ == '__main__':
340 if __name__ == '__main__':
338 main()
341 main()
General Comments 0
You need to be logged in to leave comments. Login now