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 from __future__ import print_function # for Python 2.6 compatibility
27 import distutils.ccompiler as _distutils_ccompiler
28 import fnmatch as _fnmatch
29 try: # Python 2.7 and 3.x
30 import importlib as _importlib
31 except ImportError: # Python 2.6 and earlier
32 class _Importlib (object):
33 """Minimal workarounds for functions we need
36 def import_module(name):
37 module = __import__(name)
38 for n in name.split('.')[1:]:
39 module = getattr(module, n)
41 _importlib = _Importlib()
42 import logging as _logging
44 import platform as _platform
46 import shlex as _shlex
47 import subprocess as _subprocess
50 import urllib.parse as _urllib_parse
51 except ImportError: # Python 2.x
52 import urllib as _urllib_parse # for quote()
55 if not hasattr(_shlex, 'quote'): # Python versions older than 3.3
56 # Use the undocumented pipes.quote()
57 import pipes as _pipes
58 _shlex.quote = _pipes.quote
63 # Comment out any entries you don't need
73 'hg', # Command line tool
74 #'mercurial', # Python package
76 # Build tools and packaging
78 'virtual-pypi-installer',
82 'nosetests', # Command line tool
83 'nose', # Python package
85 'sqlite3', # Command line tool
86 'sqlite3-python', # Python package
89 'ipython', # Command line tool
90 'IPython', # Python package
91 'argparse', # Useful for utility scripts
105 if _platform.system() == 'win32':
109 class InvalidCheck (KeyError):
110 def __init__(self, check):
111 super(InvalidCheck, self).__init__(check)
118 class DependencyError (Exception):
119 _default_url = 'http://software-carpentry.org/setup/'
120 _setup_urls = { # (system, version, package) glob pairs
121 ('*', '*', 'Cython'): 'http://docs.cython.org/src/quickstart/install.html',
122 ('Linux', '*', 'EasyMercurial'): 'http://easyhg.org/download.html#download-linux',
123 ('Darwin', '*', 'EasyMercurial'): 'http://easyhg.org/download.html#download-mac',
124 ('Windows', '*', 'EasyMercurial'): 'http://easyhg.org/download.html#download-windows',
125 ('*', '*', 'EasyMercurial'): 'http://easyhg.org/download.html',
126 ('*', '*', 'argparse'): 'https://pypi.python.org/pypi/argparse#installation',
127 ('*', '*', 'ash'): 'http://www.in-ulm.de/~mascheck/various/ash/',
128 ('*', '*', 'bash'): 'http://www.gnu.org/software/bash/manual/html_node/Basic-Installation.html#Basic-Installation',
129 ('Linux', '*', 'chromium'): 'http://code.google.com/p/chromium/wiki/LinuxBuildInstructions',
130 ('Darwin', '*', 'chromium'): 'http://code.google.com/p/chromium/wiki/MacBuildInstructions',
131 ('Windows', '*', 'chromium'): 'http://www.chromium.org/developers/how-tos/build-instructions-windows',
132 ('*', '*', 'chromium'): 'http://www.chromium.org/developers/how-tos',
133 ('Windows', '*', 'emacs'): 'http://www.gnu.org/software/emacs/windows/Installing-Emacs.html',
134 ('*', '*', 'emacs'): 'http://www.gnu.org/software/emacs/#Obtaining',
135 ('*', '*', 'firefox'): 'http://www.mozilla.org/en-US/firefox/new/',
136 ('Linux', '*', 'gedit'): 'http://www.linuxfromscratch.org/blfs/view/svn/gnome/gedit.html',
137 ('*', '*', 'git'): 'http://git-scm.com/downloads',
138 ('*', '*', 'google-chrome'): 'https://www.google.com/intl/en/chrome/browser/',
139 ('*', '*', 'hg'): 'http://mercurial.selenic.com/',
140 ('*', '*', 'mercurial'): 'http://mercurial.selenic.com/',
141 ('*', '*', 'IPython'): 'http://ipython.org/install.html',
142 ('*', '*', 'ipython'): 'http://ipython.org/install.html',
143 ('*', '*', 'jinja'): 'http://jinja.pocoo.org/docs/intro/#installation',
144 ('*', '*', 'kate'): 'http://kate-editor.org/get-it/',
145 ('*', '*', 'make'): 'http://www.gnu.org/software/make/',
146 ('Darwin', '*', 'matplotlib'): 'http://matplotlib.org/users/installing.html#building-on-osx',
147 ('Windows', '*', 'matplotlib'): 'http://matplotlib.org/users/installing.html#installing-on-windows',
148 ('*', '*', 'matplotlib'): 'http://matplotlib.org/users/installing.html#installing',
149 ('*', '*', 'mayavi.mlab'): 'http://docs.enthought.com/mayavi/mayavi/installation.html',
150 ('*', '*', 'nano'): 'http://www.nano-editor.org/dist/latest/faq.html#3',
151 ('*', '*', 'networkx'): 'http://networkx.github.com/documentation/latest/install.html#installing',
152 ('*', '*', 'nose'): 'https://nose.readthedocs.org/en/latest/#installation-and-quick-start',
153 ('*', '*', 'nosetests'): 'https://nose.readthedocs.org/en/latest/#installation-and-quick-start',
154 ('*', '*', 'notepad++'): 'http://notepad-plus-plus.org/download/v6.3.html',
155 ('*', '*', 'numpy'): 'http://docs.scipy.org/doc/numpy/user/install.html',
156 ('*', '*', 'pandas'): 'http://pandas.pydata.org/pandas-docs/stable/install.html',
157 ('*', '*', 'pip'): 'http://www.pip-installer.org/en/latest/installing.html',
158 ('*', '*', 'python'): 'http://www.python.org/download/releases/2.7.3/#download',
159 ('*', '*', 'pyzmq'): 'https://github.com/zeromq/pyzmq/wiki/Building-and-Installing-PyZMQ',
160 ('Linux', '*', 'scipy'): 'http://www.scipy.org/Installing_SciPy/Linux',
161 ('Darwin', '*', 'scipy'): 'http://www.scipy.org/Installing_SciPy/Mac_OS_X',
162 ('Windows', '*', 'scipy'): 'http://www.scipy.org/Installing_SciPy/Windows',
163 ('*', '*', 'scipy'): 'http://www.scipy.org/Installing_SciPy',
164 ('*', '*', 'setuptools'): 'https://pypi.python.org/pypi/setuptools#installation-instructions',
165 ('*', '*', 'sqlite3'): 'http://www.sqlite.org/download.html',
166 ('*', '*', 'sublime-text'): 'http://www.sublimetext.com/2',
167 ('*', '*', 'sympy'): 'http://docs.sympy.org/dev/install.html',
168 ('Darwin', '*', 'textmate'): 'http://macromates.com/',
169 ('Darwin', '*', 'textwrangler'): 'http://www.barebones.com/products/textwrangler/download.html',
170 ('*', '*', 'tornado'): 'http://www.tornadoweb.org/',
171 ('*', '*', 'vim'): 'http://www.vim.org/download.php',
172 ('Darwin', '*', 'xcode'): 'https://developer.apple.com/xcode/',
173 ('*', '*', 'xemacs'): 'http://www.us.xemacs.org/Install/',
174 ('*', '*', 'zsh'): 'http://www.zsh.org/',
177 def _get_message(self):
179 def _set_message(self, message):
180 self._message = message
181 message = property(_get_message, _set_message)
183 def __init__(self, checker, message, causes=None):
184 super(DependencyError, self).__init__(message)
185 self.checker = checker
186 self.message = message
192 system = _platform.system()
195 'linux_distribution',
199 value = getattr(_platform, pversion)()
203 package = self.checker.name
204 for (s,v,p),url in self._setup_urls.items():
205 if (_fnmatch.fnmatch(system, s) and
206 _fnmatch.fnmatch(version, v) and
207 _fnmatch.fnmatch(package, p)):
209 return self._default_url
214 'check for {0} failed:'.format(self.checker.full_name()),
216 ' For instructions on installing an up-to-date version, see',
220 lines.append(' causes:')
221 for cause in self.causes:
222 lines.extend(' ' + line for line in str(cause).splitlines())
223 return '\n'.join(lines)
226 def check(checks=None):
233 checker = CHECKER[check]
234 except KeyError as e:
235 raise InvalidCheck(check)# from e
236 _sys.stdout.write('check {0}...\t'.format(checker.full_name()))
238 version = checker.check()
239 except DependencyError as e:
241 _sys.stdout.write('fail\n')
243 _sys.stdout.write('pass\n')
244 successes.append((checker, version))
246 print('\nSuccesses:\n')
247 for checker,version in successes:
248 print('{0} {1}'.format(
250 version or 'unknown'))
254 for failure in failures:
255 if failure not in printed:
258 printed.append(failure)
263 class Dependency (object):
264 def __init__(self, name, long_name=None, minimum_version=None,
265 version_delimiter='.', and_dependencies=None,
266 or_dependencies=None):
268 self.long_name = long_name or name
269 self.minimum_version = minimum_version
270 self.version_delimiter = version_delimiter
271 if not and_dependencies:
272 and_dependencies = []
273 self.and_dependencies = and_dependencies
274 if not or_dependencies:
276 self.or_dependencies = or_dependencies
277 self._check_error = None
280 return '<{0} {1}>'.format(type(self).__name__, self.name)
283 if self.name == self.long_name:
286 return '{0} ({1})'.format(self.long_name, self.name)
289 if self._check_error:
290 raise self._check_error
292 self._check_dependencies()
294 except DependencyError as e:
295 self._check_error = e # cache for future calls
298 def _check_dependencies(self):
299 for dependency in self.and_dependencies:
300 if not hasattr(dependency, 'check'):
301 dependency = CHECKER[dependency]
304 except DependencyError as e:
305 raise DependencyError(
308 'some dependencies for {0} were not satisfied'
309 ).format(self.full_name()),
313 for dependency in self.or_dependencies:
314 if not hasattr(dependency, 'check'):
315 dependency = CHECKER[dependency]
317 version = dependency.check()
318 except DependencyError as e:
322 'dependency': dependency,
325 break # no need to test other dependencies
326 if self.or_dependencies and not self.or_pass:
327 raise DependencyError(
330 '{0} requires at least one of the following dependencies'
331 ).format(self.full_name()),
335 version = self._get_version()
336 parsed_version = None
337 if hasattr(self, '_get_parsed_version'):
338 parsed_version = self._get_parsed_version()
339 if self.minimum_version:
340 self._check_version(version=version, parsed_version=parsed_version)
343 def _get_version(self):
344 raise NotImplementedError(self)
346 def _minimum_version_string(self):
347 return self.version_delimiter.join(
348 str(part) for part in self.minimum_version)
350 def _check_version(self, version, parsed_version=None):
351 if not parsed_version:
352 parsed_version = self._parse_version(version=version)
353 if not parsed_version or parsed_version < self.minimum_version:
354 raise DependencyError(
356 message='outdated version of {0}: {1} (need >= {2})'.format(
357 self.full_name(), version, self._minimum_version_string()))
359 def _parse_version(self, version):
363 for part in version.split(self.version_delimiter):
365 parsed_version.append(int(part))
366 except ValueError as e:
367 raise DependencyError(
370 'unparsable {0!r} in version {1} of {2}, (need >= {3})'
372 part, version, self.full_name(),
373 self._minimum_version_string()))# from e
374 return tuple(parsed_version)
377 class PythonDependency (Dependency):
378 def __init__(self, name='python', long_name='Python version',
379 minimum_version=(2, 6), **kwargs):
380 super(PythonDependency, self).__init__(
381 name=name, long_name=long_name, minimum_version=minimum_version,
384 def _get_version(self):
387 def _get_parsed_version(self):
388 return _sys.version_info
391 CHECKER['python'] = PythonDependency()
394 class CommandDependency (Dependency):
395 exe_extension = _distutils_ccompiler.new_compiler().exe_extension
397 def __init__(self, command, paths=None, version_options=('--version',),
398 stdin=None, version_regexp=None, version_stream='stdout',
400 if 'name' not in kwargs:
401 kwargs['name'] = command
402 super(CommandDependency, self).__init__(**kwargs)
403 self.command = command
405 self.version_options = version_options
407 if not version_regexp:
408 regexp = r'([\d][\d{0}]*[\d])'.format(self.version_delimiter)
409 version_regexp = _re.compile(regexp)
410 self.version_regexp = version_regexp
411 self.version_stream = version_stream
413 def _get_command_version_stream(self, command=None, stdin=None,
416 command = self.command + (self.exe_extension or '')
420 popen_stdin = _subprocess.PIPE
424 p = _subprocess.Popen(
425 [command] + list(self.version_options), stdin=popen_stdin,
426 stdout=_subprocess.PIPE, stderr=_subprocess.PIPE,
427 universal_newlines=True)
429 raise DependencyError(
431 message="could not find '{0}' executable".format(command),
433 stdout,stderr = p.communicate(stdin)
435 if status not in expect:
437 "failed to execute: {0} {1}".format(
439 ' '.join(_shlex.quote(arg)
440 for arg in self.version_options)),
441 'status: {0}'.format(status),
443 for name,string in [('stdout', stdout), ('stderr', stderr)]:
445 lines.extend([name + ':', string])
446 raise DependencyError(checker=self, message='\n'.join(lines))
447 for name,string in [('stdout', stdout), ('stderr', stderr)]:
448 if name == self.version_stream:
450 raise DependencyError(
452 message='empty version stream on {0} for {1}'.format(
453 self.version_stream, command))
455 raise NotImplementedError(self.version_stream)
457 def _get_version_stream(self, **kwargs):
458 paths = [self.command + (self.exe_extension or '')]
459 if self.exe_extension:
460 paths.append(self.command) # also look at the extension-less path
462 paths.extend(self.paths)
466 return self._get_command_version_stream(command=path, **kwargs)
467 except DependencyError as e:
469 raise DependencyError(
471 message='errors finding {0} version'.format(
475 def _get_version(self):
476 version_stream = self._get_version_stream()
477 match = self.version_regexp.search(version_stream)
479 raise DependencyError(
481 message='no version string in output:\n{0}'.format(
483 return match.group(1)
486 def _program_files_paths(*args):
487 "Utility for generating MS Windows search paths"
488 pf = _os.environ.get('ProgramFiles', '/usr/bin')
489 pfx86 = _os.environ.get('ProgramFiles(x86)', pf)
490 paths = [_os.path.join(pf, *args)]
492 paths.append(_os.path.join(pfx86, *args))
496 for command,long_name,minimum_version,paths in [
497 ('sh', 'Bourne Shell', None, None),
498 ('ash', 'Almquist Shell', None, None),
499 ('bash', 'Bourne Again Shell', None, None),
500 ('csh', 'C Shell', None, None),
501 ('ksh', 'KornShell', None, None),
502 ('dash', 'Debian Almquist Shell', None, None),
503 ('tcsh', 'TENEX C Shell', None, None),
504 ('zsh', 'Z Shell', None, None),
505 ('git', 'Git', (1, 7, 0), None),
506 ('hg', 'Mercurial', (2, 0, 0), None),
507 ('EasyMercurial', None, (1, 3), None),
508 ('pip', None, None, None),
509 ('sqlite3', 'SQLite 3', None, None),
510 ('nosetests', 'Nose', (1, 0, 0), None),
511 ('ipython', 'IPython script', (0, 13), None),
512 ('emacs', 'Emacs', None, None),
513 ('xemacs', 'XEmacs', None, None),
514 ('vim', 'Vim', None, None),
515 ('vi', None, None, None),
516 ('nano', 'Nano', None, None),
517 ('gedit', None, None, None),
518 ('kate', 'Kate', None, None),
519 ('notepad++', 'Notepad++', None,
520 _program_files_paths('Notepad++', 'notepad++.exe')),
521 ('firefox', 'Firefox', None,
522 _program_files_paths('Mozilla Firefox', 'firefox.exe')),
523 ('google-chrome', 'Google Chrome', None,
524 _program_files_paths('Google', 'Chrome', 'Application', 'chrome.exe')
526 ('chromium', 'Chromium', None, None),
530 CHECKER[command] = CommandDependency(
531 command=command, paths=paths, long_name=long_name,
532 minimum_version=minimum_version)
533 del command, long_name, minimum_version, paths # cleanup namespace
536 class MakeDependency (CommandDependency):
537 makefile = '\n'.join([
539 '\t@echo "MAKE_VERSION=$(MAKE_VERSION)"',
540 '\t@echo "MAKE=$(MAKE)"',
544 def _get_version(self):
546 return super(MakeDependency, self)._get_version()
547 except DependencyError as e:
548 version_options = self.version_options
549 self.version_options = ['-f', '-']
551 stream = self._get_version_stream(stdin=self.makefile)
553 for line in stream.splitlines():
555 key,value = line.split('=', 1)
556 except ValueError as ve:
557 raise e# from NotImplementedError(stream)
559 if info.get('MAKE_VERSION', None):
560 return info['MAKE_VERSION']
561 elif info.get('MAKE', None):
565 self.version_options = version_options
568 CHECKER['make'] = MakeDependency(command='make', minimum_version=None)
571 class EasyInstallDependency (CommandDependency):
572 def _get_version(self):
574 return super(EasyInstallDependency, self)._get_version()
575 except DependencyError as e:
576 version_stream = self.version_stream
578 self.version_stream = 'stderr'
579 stream = self._get_version_stream(expect=(1,))
580 if 'option --version not recognized' in stream:
581 return 'unknown (possibly Setuptools?)'
583 self.version_stream = version_stream
586 CHECKER['easy_install'] = EasyInstallDependency(
587 command='easy_install', long_name='Setuptools easy_install',
588 minimum_version=None)
591 class PathCommandDependency (CommandDependency):
592 """A command that doesn't support --version or equivalent options
594 On some operating systems (e.g. OS X), a command's executable may
595 be hard to find, or not exist in the PATH. Work around that by
596 just checking for the existence of a characteristic file or
597 directory. Since the characteristic path may depend on OS,
598 installed version, etc., take a list of paths, and succeed if any
601 def _get_command_version_stream(self, *args, **kwargs):
602 raise NotImplementedError()
604 def _get_version_stream(self, *args, **kwargs):
605 raise NotImplementedError()
607 def _get_version(self):
608 for path in self.paths:
609 if _os.path.exists(path):
611 raise DependencyError(
614 'nothing exists at any of the expected paths for {0}:\n {1}'
617 '\n '.join(p for p in self.paths)))
620 for paths,name,long_name in [
621 ([_os.path.join(_ROOT_PATH, 'Applications', 'Sublime Text 2.app')],
622 'sublime-text', 'Sublime Text'),
623 ([_os.path.join(_ROOT_PATH, 'Applications', 'TextMate.app')],
624 'textmate', 'TextMate'),
625 ([_os.path.join(_ROOT_PATH, 'Applications', 'TextWrangler.app')],
626 'textwrangler', 'TextWrangler'),
627 ([_os.path.join(_ROOT_PATH, 'Applications', 'Safari.app')],
629 ([_os.path.join(_ROOT_PATH, 'Applications', 'Xcode.app'), # OS X >=1.7
630 _os.path.join(_ROOT_PATH, 'Developer', 'Applications', 'Xcode.app'
637 CHECKER[name] = PathCommandDependency(
638 command=None, paths=paths, name=name, long_name=long_name)
639 del paths, name, long_name # cleanup namespace
642 class PythonPackageDependency (Dependency):
643 def __init__(self, package, **kwargs):
644 if 'name' not in kwargs:
645 kwargs['name'] = package
646 if 'and_dependencies' not in kwargs:
647 kwargs['and_dependencies'] = []
648 if 'python' not in kwargs['and_dependencies']:
649 kwargs['and_dependencies'].append('python')
650 super(PythonPackageDependency, self).__init__(**kwargs)
651 self.package = package
653 def _get_version(self):
654 package = self._get_package(self.package)
655 return self._get_version_from_package(package)
657 def _get_package(self, package):
659 return _importlib.import_module(package)
660 except ImportError as e:
661 raise DependencyError(
663 message="could not import the '{0}' package for {1}".format(
664 package, self.full_name()),
667 def _get_version_from_package(self, package):
669 version = package.__version__
670 except AttributeError:
675 for package,name,long_name,minimum_version,and_dependencies in [
676 ('nose', None, 'Nose Python package',
677 CHECKER['nosetests'].minimum_version, None),
678 ('jinja2', 'jinja', 'Jinja', (2, 6), None),
679 ('zmq', 'pyzmq', 'PyZMQ', (2, 1, 4), None),
680 ('IPython', None, 'IPython Python package',
681 CHECKER['ipython'].minimum_version, ['jinja', 'tornado', 'pyzmq']),
682 ('argparse', None, 'Argparse', None, None),
683 ('numpy', None, 'NumPy', None, None),
684 ('scipy', None, 'SciPy', None, None),
685 ('matplotlib', None, 'Matplotlib', None, None),
686 ('pandas', None, 'Pandas', (0, 8), None),
687 ('sympy', None, 'SymPy', None, None),
688 ('Cython', None, None, None, None),
689 ('networkx', None, 'NetworkX', None, None),
690 ('mayavi.mlab', None, 'MayaVi', None, None),
691 ('setuptools', None, 'Setuptools', None, None),
699 kwargs['and_dependencies'] = and_dependencies
700 CHECKER[name] = PythonPackageDependency(
701 package=package, name=name, long_name=long_name,
702 minimum_version=minimum_version, **kwargs)
704 del package, name, long_name, minimum_version, and_dependencies, kwargs
707 class MercurialPythonPackage (PythonPackageDependency):
708 def _get_version(self):
709 try: # mercurial >= 1.2
710 package = _importlib.import_module('mercurial.util')
711 except ImportError as e: # mercurial <= 1.1.2
712 package = self._get_package('mercurial.version')
713 return package.get_version()
715 return package.version()
718 CHECKER['mercurial'] = MercurialPythonPackage(
719 package='mercurial.util', name='mercurial',
720 long_name='Mercurial Python package',
721 minimum_version=CHECKER['hg'].minimum_version)
724 class TornadoPythonPackage (PythonPackageDependency):
725 def _get_version_from_package(self, package):
726 return package.version
728 def _get_parsed_version(self):
729 package = self._get_package(self.package)
730 return package.version_info
733 CHECKER['tornado'] = TornadoPythonPackage(
734 package='tornado', name='tornado', long_name='Tornado', minimum_version=(2, 0))
737 class SQLitePythonPackage (PythonPackageDependency):
738 def _get_version_from_package(self, package):
741 def _get_parsed_version(self):
742 return _sys.version_info
745 CHECKER['sqlite3-python'] = SQLitePythonPackage(
746 package='sqlite3', name='sqlite3-python',
747 long_name='SQLite Python package',
748 minimum_version=CHECKER['sqlite3'].minimum_version)
751 class UserTaskDependency (Dependency):
752 "Prompt the user to complete a task and check for success"
753 def __init__(self, prompt, **kwargs):
754 super(UserTaskDependency, self).__init__(**kwargs)
758 if _sys.version_info >= (3, ):
759 result = input(self.prompt)
761 result = raw_input(self.prompt)
762 return self._check_result(result)
764 def _check_result(self, result):
765 raise NotImplementedError()
768 class EditorTaskDependency (UserTaskDependency):
769 def __init__(self, **kwargs):
770 self.path = _os.path.expanduser(_os.path.join(
771 '~', 'swc-installation-test.txt'))
772 self.contents = 'Hello, world!'
773 super(EditorTaskDependency, self).__init__(
775 'Open your favorite text editor and create the file\n'
777 'containing the line:\n'
779 'Press enter here after you have done this.\n'
780 'You may remove the file after you have finished testing.'
781 ).format(self.path, self.contents),
784 def _check_result(self, result):
787 with open(self.path, 'r') as f:
790 raise DependencyError(
792 message='could not open {0!r}: {1}'.format(self.path, e)
794 if contents.strip() != self.contents:
795 raise DependencyError(
798 'file contents ({0!r}) did not match the expected {1!r}'
799 ).format(contents, self.contents))
802 CHECKER['other-editor'] = EditorTaskDependency(
803 name='other-editor', long_name='')
806 class VirtualDependency (Dependency):
808 return '{0} {1}'.format(
809 self.or_pass['dependency'].full_name(),
810 self.or_pass['version'])
813 for name,long_name,dependencies in [
814 ('virtual-shell', 'command line shell', (
824 ('virtual-editor', 'text/code editor', (
836 'other-editor', # last because it requires user interaction
838 ('virtual-browser', 'web browser', (
844 ('virtual-pypi-installer', 'PyPI installer', (
849 CHECKER[name] = VirtualDependency(
850 name=name, long_name=long_name, or_dependencies=dependencies)
851 del name, long_name, dependencies # cleanup namespace
854 def _print_info(key, value, indent=19):
855 print('{0}{1}: {2}'.format(key, ' '*(indent-len(key)), value))
857 def print_system_info():
858 print("If you do not understand why the above failures occurred,")
859 print("copy and send the *entire* output (all info above and summary")
860 print("below) to the instructor for help.")
862 print('==================')
863 print('System information')
864 print('==================')
865 _print_info('os.name', _os.name)
866 _print_info('os.uname', _platform.uname())
867 _print_info('platform', _sys.platform)
868 _print_info('platform+', _platform.platform())
870 'linux_distribution',
874 value = getattr(_platform, pversion)()
876 _print_info(pversion, value)
877 _print_info('prefix', _sys.prefix)
878 _print_info('exec_prefix', _sys.exec_prefix)
879 _print_info('executable', _sys.executable)
880 _print_info('version_info', _sys.version_info)
881 _print_info('version', _sys.version)
882 _print_info('environment', '')
883 for key,value in sorted(_os.environ.items()):
884 print(' {0}={1}'.format(key, value))
885 print('==================')
887 def print_suggestions(instructor_fallback=True):
889 print('For suggestions on installing missing packages, see')
890 print('http://software-carpentry.org/setup/')
892 print('For instructings on installing a particular package,')
893 print('see the failure message for that package printed above.')
894 if instructor_fallback:
896 print('For help, email the *entire* output of this script to')
897 print('your instructor.')
900 if __name__ == '__main__':
901 import optparse as _optparse
903 parser = _optparse.OptionParser(usage='%prog [options] [check...]')
905 parser.format_epilog = lambda formatter: '\n' + epilog
907 '-v', '--verbose', action='store_true',
908 help=('print additional information to help troubleshoot '
909 'installation issues'))
910 options,args = parser.parse_args()
913 except InvalidCheck as e:
914 print("I don't know how to check for {0!r}".format(e.check))
915 print('I do know how to check for:')
916 for key,checker in sorted(CHECKER.items()):
917 if checker.long_name != checker.name:
918 print(' {0} {1}({2})'.format(
919 key, ' '*(20-len(key)), checker.long_name))
921 print(' {0}'.format(key))
927 print_suggestions(instructor_fallback=True)