3 """Test script to check for required functionality.
5 Execute this code at the command line by typing:
7 python swc-installation-test-2.py
9 Run the script and follow the instructions it prints at the end.
11 This script requires at least Python 2.6. You can check the version
12 of Python that you have installed with 'swc-installation-test-1.py'.
14 By default, this script will test for all the dependencies your
15 instructor thinks you need. If you want to test for a different set
16 of packages, you can list them on the command line. For example:
18 python swc-installation-test-2.py git virtual-editor
20 This is useful if the original test told you to install a more recent
21 version of a particular dependency, and you just want to re-test that
25 # Some details about the implementation:
27 # The dependencies are divided into a hierarchy of classes rooted on
28 # Dependency class. You can refer to the code to see which package
29 # comes under which type of dependency.
31 # The CHECKER dictionary stores information about all the dependencies
32 # and CHECKS stores list of the dependencies which are to be checked in
33 # the current workshop.
35 # In the "__name__ == '__main__'" block, we launch all the checks with
36 # check() function, which prints information about the tests as they run
37 # and details about the failures after the tests complete. In case of
38 # failure, the functions print_system_info() and print_suggestions()
39 # are called after this, where the former prints information about the
40 # user's system for debugging purposes while the latter prints some
41 # suggestions to follow.
44 from __future__ import print_function # for Python 2.6 compatibility
46 import distutils.ccompiler as _distutils_ccompiler
47 import fnmatch as _fnmatch
48 try: # Python 2.7 and 3.x
49 import importlib as _importlib
50 except ImportError: # Python 2.6 and earlier
51 class _Importlib (object):
52 """Minimal workarounds for functions we need
55 def import_module(name):
56 module = __import__(name)
57 for n in name.split('.')[1:]:
58 module = getattr(module, n)
60 _importlib = _Importlib()
61 import logging as _logging
63 import platform as _platform
65 import shlex as _shlex
66 import subprocess as _subprocess
69 import urllib.parse as _urllib_parse
70 except ImportError: # Python 2.x
71 import urllib as _urllib_parse # for quote()
72 import xml.etree.ElementTree as _element_tree
75 import distro as _distro
80 if not hasattr(_shlex, 'quote'): # Python versions older than 3.3
81 # Use the undocumented pipes.quote()
82 import pipes as _pipes
83 _shlex.quote = _pipes.quote
88 # Comment out any entries you don't need
98 'hg', # Command line tool
99 #'mercurial', # Python package
101 # Build tools and packaging
103 'virtual-pypi-installer',
107 'nosetests', # Command line tool
108 'nose', # Python package
109 'py.test', # Command line tool
110 'pytest', # Python package
112 'sqlite3', # Command line tool
113 'sqlite3-python', # Python package
116 'ipython', # Command line tool
117 'IPython', # Python package
118 'argparse', # Useful for utility scripts
132 if _platform.system() == 'win32':
136 class InvalidCheck (KeyError):
137 def __init__(self, check):
138 super(InvalidCheck, self).__init__(check)
145 class DependencyError (Exception):
146 _default_url = 'http://software-carpentry.org/setup/'
147 _setup_urls = { # (system, version, package) glob pairs
148 ('*', '*', 'Cython'): 'http://docs.cython.org/src/quickstart/install.html',
149 ('Linux', '*', 'EasyMercurial'): 'http://easyhg.org/download.html#download-linux',
150 ('Darwin', '*', 'EasyMercurial'): 'http://easyhg.org/download.html#download-mac',
151 ('Windows', '*', 'EasyMercurial'): 'http://easyhg.org/download.html#download-windows',
152 ('*', '*', 'EasyMercurial'): 'http://easyhg.org/download.html',
153 ('*', '*', 'argparse'): 'https://pypi.python.org/pypi/argparse#installation',
154 ('*', '*', 'ash'): 'http://www.in-ulm.de/~mascheck/various/ash/',
155 ('*', '*', 'bash'): 'http://www.gnu.org/software/bash/manual/html_node/Basic-Installation.html#Basic-Installation',
156 ('Linux', '*', 'chromium'): 'http://code.google.com/p/chromium/wiki/LinuxBuildInstructions',
157 ('Darwin', '*', 'chromium'): 'http://code.google.com/p/chromium/wiki/MacBuildInstructions',
158 ('Windows', '*', 'chromium'): 'http://www.chromium.org/developers/how-tos/build-instructions-windows',
159 ('*', '*', 'chromium'): 'http://www.chromium.org/developers/how-tos',
160 ('Windows', '*', 'emacs'): 'http://www.gnu.org/software/emacs/windows/Installing-Emacs.html',
161 ('*', '*', 'emacs'): 'http://www.gnu.org/software/emacs/#Obtaining',
162 ('*', '*', 'firefox'): 'http://www.mozilla.org/en-US/firefox/new/',
163 ('Linux', '*', 'gedit'): 'http://www.linuxfromscratch.org/blfs/view/svn/gnome/gedit.html',
164 ('*', '*', 'git'): 'http://git-scm.com/downloads',
165 ('*', '*', 'google-chrome'): 'https://www.google.com/intl/en/chrome/browser/',
166 ('*', '*', 'hg'): 'http://mercurial.selenic.com/',
167 ('*', '*', 'mercurial'): 'http://mercurial.selenic.com/',
168 ('*', '*', 'IPython'): 'http://ipython.org/install.html',
169 ('*', '*', 'ipython'): 'http://ipython.org/install.html',
170 ('*', '*', 'jinja'): 'http://jinja.pocoo.org/docs/intro/#installation',
171 ('*', '*', 'kate'): 'http://kate-editor.org/get-it/',
172 ('*', '*', 'make'): 'http://www.gnu.org/software/make/',
173 ('Darwin', '*', 'matplotlib'): 'http://matplotlib.org/users/installing.html#building-on-osx',
174 ('Windows', '*', 'matplotlib'): 'http://matplotlib.org/users/installing.html#installing-on-windows',
175 ('*', '*', 'matplotlib'): 'http://matplotlib.org/users/installing.html#installing',
176 ('*', '*', 'mayavi.mlab'): 'http://docs.enthought.com/mayavi/mayavi/installation.html',
177 ('*', '*', 'nano'): 'http://www.nano-editor.org/dist/latest/faq.html#3',
178 ('*', '*', 'networkx'): 'http://networkx.github.com/documentation/latest/install.html#installing',
179 ('*', '*', 'nose'): 'https://nose.readthedocs.org/en/latest/#installation-and-quick-start',
180 ('*', '*', 'nosetests'): 'https://nose.readthedocs.org/en/latest/#installation-and-quick-start',
181 ('*', '*', 'notepad++'): 'http://notepad-plus-plus.org/download/v6.3.html',
182 ('*', '*', 'numpy'): 'http://docs.scipy.org/doc/numpy/user/install.html',
183 ('*', '*', 'pandas'): 'http://pandas.pydata.org/pandas-docs/stable/install.html',
184 ('*', '*', 'pip'): 'http://www.pip-installer.org/en/latest/installing.html',
185 ('*', '*', 'pytest'): 'http://pytest.org/latest/getting-started.html',
186 ('*', '*', 'python'): 'http://www.python.org/download/releases/2.7.3/#download',
187 ('*', '*', 'pyzmq'): 'https://github.com/zeromq/pyzmq/wiki/Building-and-Installing-PyZMQ',
188 ('*', '*', 'py.test'): 'http://pytest.org/latest/getting-started.html',
189 ('Linux', '*', 'scipy'): 'http://www.scipy.org/Installing_SciPy/Linux',
190 ('Darwin', '*', 'scipy'): 'http://www.scipy.org/Installing_SciPy/Mac_OS_X',
191 ('Windows', '*', 'scipy'): 'http://www.scipy.org/Installing_SciPy/Windows',
192 ('*', '*', 'scipy'): 'http://www.scipy.org/Installing_SciPy',
193 ('*', '*', 'setuptools'): 'https://pypi.python.org/pypi/setuptools#installation-instructions',
194 ('*', '*', 'sqlite3'): 'http://www.sqlite.org/download.html',
195 ('*', '*', 'sublime-text'): 'http://www.sublimetext.com/2',
196 ('*', '*', 'sympy'): 'http://docs.sympy.org/dev/install.html',
197 ('Darwin', '*', 'textmate'): 'http://macromates.com/',
198 ('Darwin', '*', 'textwrangler'): 'http://www.barebones.com/products/textwrangler/download.html',
199 ('*', '*', 'tornado'): 'http://www.tornadoweb.org/',
200 ('*', '*', 'vim'): 'http://www.vim.org/download.php',
201 ('Darwin', '*', 'xcode'): 'https://developer.apple.com/xcode/',
202 ('*', '*', 'xemacs'): 'http://www.us.xemacs.org/Install/',
203 ('*', '*', 'zsh'): 'http://www.zsh.org/',
206 def _get_message(self):
208 def _set_message(self, message):
209 self._message = message
210 message = property(_get_message, _set_message)
212 def __init__(self, checker, message, causes=None):
213 super(DependencyError, self).__init__(message)
214 self.checker = checker
215 self.message = message
221 system = _platform.system()
223 if system == 'Linux' and _distro:
224 version = _distro.id()
227 'linux_distribution',
231 value = getattr(_platform, pversion)()
237 package = self.checker.name
238 for (s,v,p),url in self._setup_urls.items():
239 if (_fnmatch.fnmatch(system, s) and
240 _fnmatch.fnmatch(version, v) and
241 _fnmatch.fnmatch(package, p)):
243 return self._default_url
248 'check for {0} failed:'.format(self.checker.full_name()),
250 ' For instructions on installing an up-to-date version, see',
254 lines.append(' causes:')
255 for cause in self.causes:
256 lines.extend(' ' + line for line in str(cause).splitlines())
257 return '\n'.join(lines)
260 def check(checks=None):
267 checker = CHECKER[check]
268 except KeyError as e:
269 raise InvalidCheck(check)# from e
270 _sys.stdout.write('check {0}...\t'.format(checker.full_name()))
272 version = checker.check()
273 except DependencyError as e:
275 _sys.stdout.write('fail\n')
277 _sys.stdout.write('pass\n')
278 successes.append((checker, version))
280 print('\nSuccesses:\n')
281 for checker,version in successes:
282 print('{0} {1}'.format(
284 version or 'unknown'))
288 for failure in failures:
289 if failure not in printed:
292 printed.append(failure)
297 class Dependency (object):
298 def __init__(self, name, long_name=None, minimum_version=None,
299 version_delimiter='.', and_dependencies=None,
300 or_dependencies=None):
302 self.long_name = long_name or name
303 self.minimum_version = minimum_version
304 self.version_delimiter = version_delimiter
305 if not and_dependencies:
306 and_dependencies = []
307 self.and_dependencies = and_dependencies
308 if not or_dependencies:
310 self.or_dependencies = or_dependencies
311 self._check_error = None
314 return '<{0} {1}>'.format(type(self).__name__, self.name)
317 if self.name == self.long_name:
320 return '{0} ({1})'.format(self.long_name, self.name)
323 if self._check_error:
324 raise self._check_error
326 self._check_dependencies()
328 except DependencyError as e:
329 self._check_error = e # cache for future calls
332 def _check_dependencies(self):
333 for dependency in self.and_dependencies:
334 if not hasattr(dependency, 'check'):
335 dependency = CHECKER[dependency]
338 except DependencyError as e:
339 raise DependencyError(
342 'some dependencies for {0} were not satisfied'
343 ).format(self.full_name()),
347 for dependency in self.or_dependencies:
348 if not hasattr(dependency, 'check'):
349 dependency = CHECKER[dependency]
351 version = dependency.check()
352 except DependencyError as e:
356 'dependency': dependency,
359 break # no need to test other dependencies
360 if self.or_dependencies and not self.or_pass:
361 raise DependencyError(
364 '{0} requires at least one of the following dependencies'
365 ).format(self.full_name()),
369 version = self._get_version()
370 parsed_version = None
371 if hasattr(self, '_get_parsed_version'):
372 parsed_version = self._get_parsed_version()
373 if self.minimum_version:
374 self._check_version(version=version, parsed_version=parsed_version)
377 def _get_version(self):
378 raise NotImplementedError(self)
380 def _minimum_version_string(self):
381 return self.version_delimiter.join(
382 str(part) for part in self.minimum_version)
384 def _check_version(self, version, parsed_version=None):
385 if not parsed_version:
386 parsed_version = self._parse_version(version=version)
387 if not parsed_version or parsed_version < self.minimum_version:
388 raise DependencyError(
390 message='outdated version of {0}: {1} (need >= {2})'.format(
391 self.full_name(), version, self._minimum_version_string()))
393 def _parse_version(self, version):
397 for part in version.split(self.version_delimiter):
399 parsed_version.append(int(part))
400 except ValueError as e:
401 raise DependencyError(
404 'unparsable {0!r} in version {1} of {2}, (need >= {3})'
406 part, version, self.full_name(),
407 self._minimum_version_string()))# from e
408 return tuple(parsed_version)
411 class VirtualDependency (Dependency):
413 return '{0} {1}'.format(
414 self.or_pass['dependency'].full_name(),
415 self.or_pass['version'])
418 class CommandDependency (Dependency):
419 exe_extension = _distutils_ccompiler.new_compiler().exe_extension
421 def __init__(self, command, paths=None, version_options=('--version',),
422 stdin=None, version_regexp=None, version_stream='stdout',
424 if 'name' not in kwargs:
425 kwargs['name'] = command
426 super(CommandDependency, self).__init__(**kwargs)
427 self.command = command
429 self.version_options = version_options
431 if not version_regexp:
432 regexp = r'([\d][\d{0}]*[\d])'.format(self.version_delimiter)
433 version_regexp = _re.compile(regexp)
434 self.version_regexp = version_regexp
435 self.version_stream = version_stream
437 def _get_command_version_stream(self, command=None, stdin=None,
440 command = self.command + (self.exe_extension or '')
444 popen_stdin = _subprocess.PIPE
448 p = _subprocess.Popen(
449 [command] + list(self.version_options), stdin=popen_stdin,
450 stdout=_subprocess.PIPE, stderr=_subprocess.PIPE,
451 universal_newlines=True)
453 raise DependencyError(
455 message="could not find '{0}' executable".format(command),
457 stdout,stderr = p.communicate(stdin)
459 if status not in expect:
461 "failed to execute: {0} {1}".format(
463 ' '.join(_shlex.quote(arg)
464 for arg in self.version_options)),
465 'status: {0}'.format(status),
467 for name,string in [('stdout', stdout), ('stderr', stderr)]:
469 lines.extend([name + ':', string])
470 raise DependencyError(checker=self, message='\n'.join(lines))
471 for name,string in [('stdout', stdout), ('stderr', stderr)]:
472 if name == self.version_stream:
474 raise DependencyError(
476 message='empty version stream on {0} for {1}'.format(
477 self.version_stream, command))
479 raise NotImplementedError(self.version_stream)
481 def _get_version_stream(self, **kwargs):
482 paths = [self.command + (self.exe_extension or '')]
483 if self.exe_extension:
484 paths.append(self.command) # also look at the extension-less path
486 paths.extend(self.paths)
490 return self._get_command_version_stream(command=path, **kwargs)
491 except DependencyError as e:
493 raise DependencyError(
495 message='errors finding {0} version'.format(
499 def _get_version(self):
500 version_stream = self._get_version_stream()
501 match = self.version_regexp.search(version_stream)
503 raise DependencyError(
505 message='no version string in output:\n{0}'.format(
507 return match.group(1)
510 class VersionPlistCommandDependency (CommandDependency):
511 """A command that doesn't support --version or equivalent options
513 On OS X, a command's executable may be hard to find, or not exist
514 in the PATH. Work around that by looking up the version
515 information in the package's version.plist file.
517 def __init__(self, key='CFBundleShortVersionString', **kwargs):
518 super(VersionPlistCommandDependency, self).__init__(**kwargs)
521 def _get_command_version_stream(self, *args, **kwargs):
522 raise NotImplementedError()
524 def _get_version_stream(self, *args, **kwargs):
525 raise NotImplementedError()
528 def _get_parent(root, element):
529 """Returns the parent of this element or None for the root element
531 for node in root.iter():
534 raise ValueError((root, element))
537 def _get_next(cls, root, element):
538 """Returns the following sibling of this element or None
540 parent = cls._get_parent(root=root, element=element)
541 siblings = iter(parent)
542 for node in siblings:
545 return next(siblings)
546 except StopIteration:
550 def _get_version_from_plist(self, path):
551 """Parse the plist and return the value string for self.key
553 tree = _element_tree.parse(source=path)
555 for key in tree.findall('.//key'):
556 value = self._get_next(root=tree, element=key)
557 if value.tag != 'string':
558 raise ValueError((tree, key, value))
559 data[key.text] = value.text
560 return data[self.key]
562 def _get_version(self):
563 for path in self.paths:
564 if _os.path.exists(path):
565 return self._get_version_from_plist(path=path)
566 raise DependencyError(
569 'nothing exists at any of the expected paths for {0}:\n {1}'
572 '\n '.join(p for p in self.paths)))
575 class UserTaskDependency (Dependency):
576 "Prompt the user to complete a task and check for success"
577 def __init__(self, prompt, **kwargs):
578 super(UserTaskDependency, self).__init__(**kwargs)
582 if _sys.version_info >= (3, ):
583 result = input(self.prompt)
585 result = raw_input(self.prompt)
586 return self._check_result(result)
588 def _check_result(self, result):
589 raise NotImplementedError()
592 class EditorTaskDependency (UserTaskDependency):
593 def __init__(self, **kwargs):
594 self.path = _os.path.expanduser(_os.path.join(
595 '~', 'swc-installation-test.txt'))
596 self.contents = 'Hello, world!'
597 super(EditorTaskDependency, self).__init__(
599 'Open your favorite text editor and create the file\n'
601 'containing the line:\n'
603 'Press enter here after you have done this.\n'
604 'You may remove the file after you have finished testing.'
605 ).format(self.path, self.contents),
608 def _check_result(self, result):
611 with open(self.path, 'r') as f:
614 raise DependencyError(
616 message='could not open {0!r}: {1}'.format(self.path, e)
618 if contents.strip() != self.contents:
619 raise DependencyError(
622 'file contents ({0!r}) did not match the expected {1!r}'
623 ).format(contents, self.contents))
626 class MakeDependency (CommandDependency):
627 makefile = '\n'.join([
629 '\t@echo "MAKE_VERSION=$(MAKE_VERSION)"',
630 '\t@echo "MAKE=$(MAKE)"',
634 def _get_version(self):
636 return super(MakeDependency, self)._get_version()
637 except DependencyError as e:
638 version_options = self.version_options
639 self.version_options = ['-f', '-']
641 stream = self._get_version_stream(stdin=self.makefile)
643 for line in stream.splitlines():
645 key,value = line.split('=', 1)
646 except ValueError as ve:
647 raise e# from NotImplementedError(stream)
649 if info.get('MAKE_VERSION', None):
650 return info['MAKE_VERSION']
651 elif info.get('MAKE', None):
655 self.version_options = version_options
658 class EasyInstallDependency (CommandDependency):
659 def _get_version(self):
661 return super(EasyInstallDependency, self)._get_version()
662 except DependencyError as e:
663 version_stream = self.version_stream
665 self.version_stream = 'stderr'
666 stream = self._get_version_stream(expect=(1,))
667 if 'option --version not recognized' in stream:
668 return 'unknown (possibly Setuptools?)'
670 self.version_stream = version_stream
673 class PythonDependency (Dependency):
674 def __init__(self, name='python', long_name='Python version',
675 minimum_version=(2, 6), **kwargs):
676 super(PythonDependency, self).__init__(
677 name=name, long_name=long_name, minimum_version=minimum_version,
680 def _get_version(self):
683 def _get_parsed_version(self):
684 return _sys.version_info
687 class PythonPackageDependency (Dependency):
688 def __init__(self, package, **kwargs):
689 if 'name' not in kwargs:
690 kwargs['name'] = package
691 if 'and_dependencies' not in kwargs:
692 kwargs['and_dependencies'] = []
693 if 'python' not in kwargs['and_dependencies']:
694 kwargs['and_dependencies'].append('python')
695 super(PythonPackageDependency, self).__init__(**kwargs)
696 self.package = package
698 def _get_version(self):
699 package = self._get_package(self.package)
700 return self._get_version_from_package(package)
702 def _get_package(self, package):
704 return _importlib.import_module(package)
705 except ImportError as e:
706 raise DependencyError(
708 message="could not import the '{0}' package for {1}".format(
709 package, self.full_name()),
712 def _get_version_from_package(self, package):
714 version = package.__version__
715 except AttributeError:
720 class MercurialPythonPackage (PythonPackageDependency):
721 def _get_version(self):
722 try: # mercurial >= 1.2
723 package = _importlib.import_module('mercurial.util')
724 except ImportError as e: # mercurial <= 1.1.2
725 package = self._get_package('mercurial.version')
726 return package.get_version()
728 return package.version()
731 class TornadoPythonPackage (PythonPackageDependency):
732 def _get_version_from_package(self, package):
733 return package.version
735 def _get_parsed_version(self):
736 package = self._get_package(self.package)
737 return package.version_info
740 class SQLitePythonPackage (PythonPackageDependency):
741 def _get_version_from_package(self, package):
744 def _get_parsed_version(self):
745 return _sys.version_info
748 def _program_files_paths(*args):
749 "Utility for generating MS Windows search paths"
750 pf = _os.environ.get('ProgramFiles', '/usr/bin')
751 pfx86 = _os.environ.get('ProgramFiles(x86)', pf)
752 paths = [_os.path.join(pf, *args)]
754 paths.append(_os.path.join(pfx86, *args))
758 CHECKER['python'] = PythonDependency()
761 for command,long_name,minimum_version,paths in [
762 ('sh', 'Bourne Shell', None, None),
763 ('ash', 'Almquist Shell', None, None),
764 ('bash', 'Bourne Again Shell', None, None),
765 ('csh', 'C Shell', None, None),
766 ('ksh', 'KornShell', None, None),
767 ('dash', 'Debian Almquist Shell', None, None),
768 ('tcsh', 'TENEX C Shell', None, None),
769 ('zsh', 'Z Shell', None, None),
770 ('git', 'Git', (1, 7, 0), None),
771 ('hg', 'Mercurial', (2, 0, 0), None),
772 ('EasyMercurial', None, (1, 3), None),
773 ('pip', None, None, None),
774 ('sqlite3', 'SQLite 3', None, None),
775 ('nosetests', 'Nose', (1, 0, 0), None),
776 ('ipython', 'IPython script', (1, 0), None),
777 ('emacs', 'Emacs', None, None),
778 ('xemacs', 'XEmacs', None, None),
779 ('vim', 'Vim', None, None),
780 ('vi', None, None, None),
781 ('nano', 'Nano', None, None),
782 ('gedit', None, None, None),
783 ('kate', 'Kate', None, None),
784 ('notepad++', 'Notepad++', None,
785 _program_files_paths('Notepad++', 'notepad++.exe')),
786 ('firefox', 'Firefox', None,
787 _program_files_paths('Mozilla Firefox', 'firefox.exe')),
788 ('google-chrome', 'Google Chrome', None,
789 _program_files_paths('Google', 'Chrome', 'Application', 'chrome.exe')
791 ('chromium', 'Chromium', None, None),
795 CHECKER[command] = CommandDependency(
796 command=command, paths=paths, long_name=long_name,
797 minimum_version=minimum_version)
798 del command, long_name, minimum_version, paths # cleanup namespace
801 CHECKER['make'] = MakeDependency(command='make', minimum_version=None)
804 CHECKER['easy_install'] = EasyInstallDependency(
805 command='easy_install', long_name='Setuptools easy_install',
806 minimum_version=None)
809 CHECKER['py.test'] = CommandDependency(
810 command='py.test', version_stream='stderr',
811 minimum_version=None)
814 for paths,name,long_name in [
815 ([_os.path.join(_ROOT_PATH, 'Applications', 'Sublime Text 2.app',
816 'Contents', 'version.plist')],
817 'sublime-text', 'Sublime Text'),
818 ([_os.path.join(_ROOT_PATH, 'Applications', 'TextMate.app',
819 'Contents', 'version.plist')],
820 'textmate', 'TextMate'),
821 ([_os.path.join(_ROOT_PATH, 'Applications', 'TextWrangler.app',
822 'Contents', 'version.plist')],
823 'textwrangler', 'TextWrangler'),
824 ([_os.path.join(_ROOT_PATH, 'Applications', 'Safari.app',
825 'Contents', 'version.plist')],
827 ([_os.path.join(_ROOT_PATH, 'Applications', 'Xcode.app',
828 'Contents', 'version.plist'), # OS X >=1.7
829 _os.path.join(_ROOT_PATH, 'Developer', 'Applications', 'Xcode.app',
830 'Contents', 'version.plist'), # OS X 1.6,
836 CHECKER[name] = VersionPlistCommandDependency(
837 command=None, paths=paths, name=name, long_name=long_name)
838 del paths, name, long_name # cleanup namespace
841 for package,name,long_name,minimum_version,and_dependencies in [
842 ('nose', None, 'Nose Python package',
843 CHECKER['nosetests'].minimum_version, None),
844 ('pytest', None, 'pytest Python package',
845 CHECKER['py.test'].minimum_version, None),
846 ('jinja2', 'jinja', 'Jinja', (2, 6), None),
847 ('zmq', 'pyzmq', 'PyZMQ', (2, 1, 4), None),
848 ('IPython', None, 'IPython Python package',
849 CHECKER['ipython'].minimum_version, [
854 name='virtual-browser-ipython',
855 long_name='IPython-compatible web browser',
858 command=CHECKER['firefox'].command,
859 paths=CHECKER['firefox'].paths,
860 name='{0}-for-ipython'.format(
861 CHECKER['firefox'].name),
862 long_name='{0} for IPython'.format(
863 CHECKER['firefox'].long_name),
864 minimum_version=(6, 0)),
866 command=CHECKER['google-chrome'].command,
867 paths=CHECKER['google-chrome'].paths,
868 name='{0}-for-ipython'.format(
869 CHECKER['google-chrome'].name),
870 long_name='{0} for IPython'.format(
871 CHECKER['google-chrome'].long_name),
872 minimum_version=(13, 0)),
874 command=CHECKER['chromium'].command,
875 paths=CHECKER['chromium'].paths,
876 name='{0}-for-ipython'.format(
877 CHECKER['chromium'].name),
878 long_name='{0} for IPython'.format(
879 CHECKER['chromium'].long_name),
880 minimum_version=(13, 0)),
881 VersionPlistCommandDependency(
882 command=CHECKER['safari'].command,
883 paths=CHECKER['safari'].paths,
884 key=CHECKER['safari'].key,
885 name='{0}-for-ipython'.format(
886 CHECKER['safari'].name),
887 long_name='{0} for IPython'.format(
888 CHECKER['safari'].long_name),
889 minimum_version=(5, 0)),
892 ('argparse', None, 'Argparse', None, None),
893 ('numpy', None, 'NumPy', None, None),
894 ('scipy', None, 'SciPy', None, None),
895 ('matplotlib', None, 'Matplotlib', None, None),
896 ('pandas', None, 'Pandas', (0, 8), None),
897 ('sympy', None, 'SymPy', None, None),
898 ('Cython', None, None, None, None),
899 ('networkx', None, 'NetworkX', None, None),
900 ('mayavi.mlab', None, 'MayaVi', None, None),
901 ('setuptools', None, 'Setuptools', None, None),
909 kwargs['and_dependencies'] = and_dependencies
910 CHECKER[name] = PythonPackageDependency(
911 package=package, name=name, long_name=long_name,
912 minimum_version=minimum_version, **kwargs)
914 del package, name, long_name, minimum_version, and_dependencies, kwargs
917 CHECKER['mercurial'] = MercurialPythonPackage(
918 package='mercurial.util', name='mercurial',
919 long_name='Mercurial Python package',
920 minimum_version=CHECKER['hg'].minimum_version)
923 CHECKER['tornado'] = TornadoPythonPackage(
924 package='tornado', name='tornado', long_name='Tornado', minimum_version=(2, 0))
927 CHECKER['sqlite3-python'] = SQLitePythonPackage(
928 package='sqlite3', name='sqlite3-python',
929 long_name='SQLite Python package',
930 minimum_version=CHECKER['sqlite3'].minimum_version)
933 CHECKER['other-editor'] = EditorTaskDependency(
934 name='other-editor', long_name='')
937 for name,long_name,dependencies in [
938 ('virtual-shell', 'command line shell', (
948 ('virtual-editor', 'text/code editor', (
960 'other-editor', # last because it requires user interaction
962 ('virtual-browser', 'web browser', (
968 ('virtual-pypi-installer', 'PyPI installer', (
973 CHECKER[name] = VirtualDependency(
974 name=name, long_name=long_name, or_dependencies=dependencies)
975 del name, long_name, dependencies # cleanup namespace
978 def _print_info(key, value, indent=19):
979 print('{0}{1}: {2}'.format(key, ' '*(indent-len(key)), value))
981 def print_system_info():
982 print("If you do not understand why the above failures occurred,")
983 print("copy and send the *entire* output (all info above and summary")
984 print("below) to the instructor for help.")
986 print('==================')
987 print('System information')
988 print('==================')
989 _print_info('os.name', _os.name)
990 _print_info('os.uname', _platform.uname())
991 _print_info('platform', _sys.platform)
992 _print_info('platform+', _platform.platform())
993 system = _platform.system()
994 if system == 'Linux' and _distro:
996 'linux_distribution',
997 _distro.name(pretty=True) or _distro.linux_distribution())
1000 'linux_distribution',
1004 value = getattr(_platform, pversion)()
1006 _print_info(pversion, value)
1007 _print_info('prefix', _sys.prefix)
1008 _print_info('exec_prefix', _sys.exec_prefix)
1009 _print_info('executable', _sys.executable)
1010 _print_info('version_info', _sys.version_info)
1011 _print_info('version', _sys.version)
1012 _print_info('environment', '')
1013 for key,value in sorted(_os.environ.items()):
1014 print(' {0}={1}'.format(key, value))
1015 print('==================')
1017 def print_suggestions(instructor_fallback=True):
1019 print('For suggestions on installing missing packages, see')
1020 print('http://software-carpentry.org/setup/')
1022 print('For instructings on installing a particular package,')
1023 print('see the failure message for that package printed above.')
1024 if instructor_fallback:
1026 print('For help, email the *entire* output of this script to')
1027 print('your instructor.')
1030 if __name__ == '__main__':
1031 import optparse as _optparse
1033 parser = _optparse.OptionParser(usage='%prog [options] [check...]')
1035 parser.format_epilog = lambda formatter: '\n' + epilog
1037 '-v', '--verbose', action='store_true',
1038 help=('print additional information to help troubleshoot '
1039 'installation issues'))
1040 options,args = parser.parse_args()
1042 passed = check(args)
1043 except InvalidCheck as e:
1044 print("I don't know how to check for {0!r}".format(e.check))
1045 print('I do know how to check for:')
1046 for key,checker in sorted(CHECKER.items()):
1047 if checker.long_name != checker.name:
1048 print(' {0} {1}({2})'.format(
1049 key, ' '*(20-len(key)), checker.long_name))
1051 print(' {0}'.format(key))
1057 print_suggestions(instructor_fallback=True)