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 class InvalidCheck (KeyError):
106 def __init__(self, check):
107 super(InvalidCheck, self).__init__(check)
114 class DependencyError (Exception):
115 _default_url = 'http://software-carpentry.org/setup/'
116 _setup_urls = { # (system, version, package) glob pairs
117 ('*', '*', 'Cython'): 'http://docs.cython.org/src/quickstart/install.html',
118 ('Linux', '*', 'EasyMercurial'): 'http://easyhg.org/download.html#download-linux',
119 ('Darwin', '*', 'EasyMercurial'): 'http://easyhg.org/download.html#download-mac',
120 ('Windows', '*', 'EasyMercurial'): 'http://easyhg.org/download.html#download-windows',
121 ('*', '*', 'EasyMercurial'): 'http://easyhg.org/download.html',
122 ('*', '*', 'argparse'): 'https://pypi.python.org/pypi/argparse#installation',
123 ('*', '*', 'ash'): 'http://www.in-ulm.de/~mascheck/various/ash/',
124 ('*', '*', 'bash'): 'http://www.gnu.org/software/bash/manual/html_node/Basic-Installation.html#Basic-Installation',
125 ('Linux', '*', 'chromium'): 'http://code.google.com/p/chromium/wiki/LinuxBuildInstructions',
126 ('Darwin', '*', 'chromium'): 'http://code.google.com/p/chromium/wiki/MacBuildInstructions',
127 ('Windows', '*', 'chromium'): 'http://www.chromium.org/developers/how-tos/build-instructions-windows',
128 ('*', '*', 'chromium'): 'http://www.chromium.org/developers/how-tos',
129 ('Windows', '*', 'emacs'): 'http://www.gnu.org/software/emacs/windows/Installing-Emacs.html',
130 ('*', '*', 'emacs'): 'http://www.gnu.org/software/emacs/#Obtaining',
131 ('*', '*', 'firefox'): 'http://www.mozilla.org/en-US/firefox/new/',
132 ('Linux', '*', 'gedit'): 'http://www.linuxfromscratch.org/blfs/view/svn/gnome/gedit.html',
133 ('*', '*', 'git'): 'http://git-scm.com/downloads',
134 ('*', '*', 'google-chrome'): 'https://www.google.com/intl/en/chrome/browser/',
135 ('*', '*', 'hg'): 'http://mercurial.selenic.com/',
136 ('*', '*', 'mercurial'): 'http://mercurial.selenic.com/',
137 ('*', '*', 'IPython'): 'http://ipython.org/install.html',
138 ('*', '*', 'ipython'): 'http://ipython.org/install.html',
139 ('*', '*', 'jinja'): 'http://jinja.pocoo.org/docs/intro/#installation',
140 ('*', '*', 'kate'): 'http://kate-editor.org/get-it/',
141 ('*', '*', 'make'): 'http://www.gnu.org/software/make/',
142 ('Darwin', '*', 'matplotlib'): 'http://matplotlib.org/users/installing.html#building-on-osx',
143 ('Windows', '*', 'matplotlib'): 'http://matplotlib.org/users/installing.html#installing-on-windows',
144 ('*', '*', 'matplotlib'): 'http://matplotlib.org/users/installing.html#installing',
145 ('*', '*', 'mayavi.mlab'): 'http://docs.enthought.com/mayavi/mayavi/installation.html',
146 ('*', '*', 'nano'): 'http://www.nano-editor.org/dist/latest/faq.html#3',
147 ('*', '*', 'networkx'): 'http://networkx.github.com/documentation/latest/install.html#installing',
148 ('*', '*', 'nose'): 'https://nose.readthedocs.org/en/latest/#installation-and-quick-start',
149 ('*', '*', 'nosetests'): 'https://nose.readthedocs.org/en/latest/#installation-and-quick-start',
150 ('*', '*', 'notepad++'): 'http://notepad-plus-plus.org/download/v6.3.html',
151 ('*', '*', 'numpy'): 'http://docs.scipy.org/doc/numpy/user/install.html',
152 ('*', '*', 'pandas'): 'http://pandas.pydata.org/pandas-docs/stable/install.html',
153 ('*', '*', 'pip'): 'http://www.pip-installer.org/en/latest/installing.html',
154 ('*', '*', 'python'): 'http://www.python.org/download/releases/2.7.3/#download',
155 ('*', '*', 'pyzmq'): 'https://github.com/zeromq/pyzmq/wiki/Building-and-Installing-PyZMQ',
156 ('Linux', '*', 'scipy'): 'http://www.scipy.org/Installing_SciPy/Linux',
157 ('Darwin', '*', 'scipy'): 'http://www.scipy.org/Installing_SciPy/Mac_OS_X',
158 ('Windows', '*', 'scipy'): 'http://www.scipy.org/Installing_SciPy/Windows',
159 ('*', '*', 'scipy'): 'http://www.scipy.org/Installing_SciPy',
160 ('*', '*', 'setuptools'): 'https://pypi.python.org/pypi/setuptools#installation-instructions',
161 ('*', '*', 'sqlite3'): 'http://www.sqlite.org/download.html',
162 ('*', '*', 'sublime-text'): 'http://www.sublimetext.com/2',
163 ('*', '*', 'sympy'): 'http://docs.sympy.org/dev/install.html',
164 ('Darwin', '*', 'textmate'): 'http://macromates.com/',
165 ('Darwin', '*', 'textwrangler'): 'http://www.barebones.com/products/textwrangler/download.html',
166 ('*', '*', 'tornado'): 'http://www.tornadoweb.org/',
167 ('*', '*', 'vim'): 'http://www.vim.org/download.php',
168 ('Darwin', '*', 'xcode'): 'https://developer.apple.com/xcode/',
169 ('*', '*', 'xemacs'): 'http://www.us.xemacs.org/Install/',
170 ('*', '*', 'zsh'): 'http://www.zsh.org/',
173 def _get_message(self):
175 def _set_message(self, message):
176 self._message = message
177 message = property(_get_message, _set_message)
179 def __init__(self, checker, message, causes=None):
180 super(DependencyError, self).__init__(message)
181 self.checker = checker
182 self.message = message
188 system = _platform.system()
191 'linux_distribution',
195 value = getattr(_platform, pversion)()
199 package = self.checker.name
200 for (s,v,p),url in self._setup_urls.items():
201 if (_fnmatch.fnmatch(system, s) and
202 _fnmatch.fnmatch(version, v) and
203 _fnmatch.fnmatch(package, p)):
205 return self._default_url
210 'check for {0} failed:'.format(self.checker.full_name()),
212 ' For instructions on installing an up-to-date version, see',
216 lines.append(' causes:')
217 for cause in self.causes:
218 lines.extend(' ' + line for line in str(cause).splitlines())
219 return '\n'.join(lines)
222 def check(checks=None):
229 checker = CHECKER[check]
230 except KeyError as e:
231 raise InvalidCheck(check)# from e
232 _sys.stdout.write('check {0}...\t'.format(checker.full_name()))
234 version = checker.check()
235 except DependencyError as e:
237 _sys.stdout.write('fail\n')
239 _sys.stdout.write('pass\n')
240 successes.append((checker, version))
242 print('\nSuccesses:\n')
243 for checker,version in successes:
244 print('{0} {1}'.format(
246 version or 'unknown'))
250 for failure in failures:
251 if failure not in printed:
254 printed.append(failure)
259 class Dependency (object):
260 def __init__(self, name, long_name=None, minimum_version=None,
261 version_delimiter='.', and_dependencies=None,
262 or_dependencies=None):
264 self.long_name = long_name or name
265 self.minimum_version = minimum_version
266 self.version_delimiter = version_delimiter
267 if not and_dependencies:
268 and_dependencies = []
269 self.and_dependencies = and_dependencies
270 if not or_dependencies:
272 self.or_dependencies = or_dependencies
273 self._check_error = None
276 return '<{0} {1}>'.format(type(self).__name__, self.name)
279 if self.name == self.long_name:
282 return '{0} ({1})'.format(self.long_name, self.name)
285 if self._check_error:
286 raise self._check_error
288 self._check_dependencies()
290 except DependencyError as e:
291 self._check_error = e # cache for future calls
294 def _check_dependencies(self):
295 for dependency in self.and_dependencies:
296 if not hasattr(dependency, 'check'):
297 dependency = CHECKER[dependency]
300 except DependencyError as e:
301 raise DependencyError(
304 'some dependencies for {0} were not satisfied'
305 ).format(self.full_name()),
309 for dependency in self.or_dependencies:
310 if not hasattr(dependency, 'check'):
311 dependency = CHECKER[dependency]
313 version = dependency.check()
314 except DependencyError as e:
318 'dependency': dependency,
321 break # no need to test other dependencies
322 if self.or_dependencies and not self.or_pass:
323 raise DependencyError(
326 '{0} requires at least one of the following dependencies'
327 ).format(self.full_name()),
331 version = self._get_version()
332 parsed_version = None
333 if hasattr(self, '_get_parsed_version'):
334 parsed_version = self._get_parsed_version()
335 if self.minimum_version:
336 self._check_version(version=version, parsed_version=parsed_version)
339 def _get_version(self):
340 raise NotImplementedError(self)
342 def _minimum_version_string(self):
343 return self.version_delimiter.join(
344 str(part) for part in self.minimum_version)
346 def _check_version(self, version, parsed_version=None):
347 if not parsed_version:
348 parsed_version = self._parse_version(version=version)
349 if not parsed_version or parsed_version < self.minimum_version:
350 raise DependencyError(
352 message='outdated version of {0}: {1} (need >= {2})'.format(
353 self.full_name(), version, self._minimum_version_string()))
355 def _parse_version(self, version):
359 for part in version.split(self.version_delimiter):
361 parsed_version.append(int(part))
362 except ValueError as e:
363 raise DependencyError(
366 'unparsable {0!r} in version {1} of {2}, (need >= {3})'
368 part, version, self.full_name(),
369 self._minimum_version_string()))# from e
370 return tuple(parsed_version)
373 class PythonDependency (Dependency):
374 def __init__(self, name='python', long_name='Python version',
375 minimum_version=(2, 6), **kwargs):
376 super(PythonDependency, self).__init__(
377 name=name, long_name=long_name, minimum_version=minimum_version,
380 def _get_version(self):
383 def _get_parsed_version(self):
384 return _sys.version_info
387 CHECKER['python'] = PythonDependency()
390 class CommandDependency (Dependency):
391 exe_extension = _distutils_ccompiler.new_compiler().exe_extension
393 def __init__(self, command, version_options=('--version',), stdin=None,
394 version_regexp=None, version_stream='stdout', **kwargs):
395 if 'name' not in kwargs:
396 kwargs['name'] = command
397 super(CommandDependency, self).__init__(**kwargs)
398 self.command = command
399 self.version_options = version_options
401 if not version_regexp:
402 regexp = r'([\d][\d{0}]*[\d])'.format(self.version_delimiter)
403 version_regexp = _re.compile(regexp)
404 self.version_regexp = version_regexp
405 self.version_stream = version_stream
407 def _get_version_stream(self, stdin=None, expect=(0,)):
411 popen_stdin = _subprocess.PIPE
414 command = self.command + (self.exe_extension or '')
416 p = _subprocess.Popen(
417 [command] + list(self.version_options), stdin=popen_stdin,
418 stdout=_subprocess.PIPE, stderr=_subprocess.PIPE,
419 universal_newlines=True)
421 raise DependencyError(
423 message="could not find '{0}' executable".format(command),
425 stdout,stderr = p.communicate(stdin)
427 if status not in expect:
429 "failed to execute: {0} {1}".format(
431 ' '.join(_shlex.quote(arg)
432 for arg in self.version_options)),
433 'status: {0}'.format(status),
435 for name,string in [('stdout', stdout), ('stderr', stderr)]:
437 lines.extend([name + ':', string])
438 raise DependencyError(checker=self, message='\n'.join(lines))
439 for name,string in [('stdout', stdout), ('stderr', stderr)]:
440 if name == self.version_stream:
442 raise NotImplementedError(self.version_stream)
444 def _get_version(self):
445 version_stream = self._get_version_stream()
446 match = self.version_regexp.search(version_stream)
448 raise DependencyError(
450 message='no version string in output:\n{0}'.format(
452 return match.group(1)
455 for command,long_name,minimum_version in [
456 ('sh', 'Bourne Shell', None),
457 ('ash', 'Almquist Shell', None),
458 ('bash', 'Bourne Again Shell', None),
459 ('csh', 'C Shell', None),
460 ('ksh', 'KornShell', None),
461 ('dash', 'Debian Almquist Shell', None),
462 ('tcsh', 'TENEX C Shell', None),
463 ('zsh', 'Z Shell', None),
464 ('git', 'Git', (1, 7, 0)),
465 ('hg', 'Mercurial', (2, 0, 0)),
466 ('EasyMercurial', None, (1, 3)),
468 ('sqlite3', 'SQLite 3', None),
469 ('nosetests', 'Nose', (1, 0, 0)),
470 ('ipython', 'IPython script', (0, 13)),
471 ('emacs', 'Emacs', None),
472 ('xemacs', 'XEmacs', None),
473 ('vim', 'Vim', None),
475 ('nano', 'Nano', None),
476 ('gedit', None, None),
477 ('kate', 'Kate', None),
478 ('notepad++', 'Notepad++', None),
479 ('firefox', 'Firefox', None),
480 ('google-chrome', 'Google Chrome', None),
481 ('chromium', 'Chromium', None),
485 CHECKER[command] = CommandDependency(
486 command=command, long_name=long_name, minimum_version=minimum_version)
487 del command, long_name, minimum_version # cleanup namespace
490 class MakeDependency (CommandDependency):
491 makefile = '\n'.join([
493 '\t@echo "MAKE_VERSION=$(MAKE_VERSION)"',
494 '\t@echo "MAKE=$(MAKE)"',
498 def _get_version(self):
500 return super(MakeDependency, self)._get_version()
501 except DependencyError as e:
502 version_options = self.version_options
503 self.version_options = ['-f', '-']
505 stream = self._get_version_stream(stdin=self.makefile)
507 for line in stream.splitlines():
509 key,value = line.split('=', 1)
510 except ValueError as ve:
511 raise e# from NotImplementedError(stream)
513 if info.get('MAKE_VERSION', None):
514 return info['MAKE_VERSION']
515 elif info.get('MAKE', None):
519 self.version_options = version_options
522 CHECKER['make'] = MakeDependency(command='make', minimum_version=None)
525 class EasyInstallDependency (CommandDependency):
526 def _get_version(self):
528 return super(EasyInstallDependency, self)._get_version()
529 except DependencyError as e:
530 version_stream = self.version_stream
532 self.version_stream = 'stderr'
533 stream = self._get_version_stream(expect=(1,))
534 if 'option --version not recognized' in stream:
535 return 'unknown (possibly Setuptools?)'
537 self.version_stream = version_stream
540 CHECKER['easy_install'] = EasyInstallDependency(
541 command='easy_install', long_name='Setuptools easy_install',
542 minimum_version=None)
545 class PathCommandDependency (CommandDependency):
546 """A command that doesn't support --version or equivalent options
548 On some operating systems (e.g. OS X), a command's executable may
549 be hard to find, or not exist in the PATH. Work around that by
550 just checking for the existence of a characteristic file or
551 directory. Since the characteristic path may depend on OS,
552 installed version, etc., take a list of paths, and succeed if any
555 def __init__(self, paths, **kwargs):
556 super(PathCommandDependency, self).__init__(self, **kwargs)
559 def _get_version_stream(self, *args, **kwargs):
560 raise NotImplementedError()
562 def _get_version(self):
563 for path in self.paths:
564 if _os.path.exists(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 for paths,name,long_name in [
576 ([_os.path.join(_os.sep, 'Applications', 'Sublime Text 2.app')],
577 'sublime-text', 'Sublime Text'),
578 ([_os.path.join(_os.sep, 'Applications', 'TextMate.app')],
579 'textmate', 'TextMate'),
580 ([_os.path.join(_os.sep, 'Applications', 'TextWrangler.app')],
581 'textwrangler', 'TextWrangler'),
582 ([_os.path.join(_os.sep, 'Applications', 'Safari.app')],
584 ([_os.path.join(_os.sep, 'Applications', 'Xcode.app'), # OS X >=1.7
585 _os.path.join(_os.sep, 'Developer', 'Applications', 'Xcode.app'
592 CHECKER[name] = PathCommandDependency(
593 paths=paths, name=name, long_name=long_name)
594 del paths, name, long_name # cleanup namespace
597 class PythonPackageDependency (Dependency):
598 def __init__(self, package, **kwargs):
599 if 'name' not in kwargs:
600 kwargs['name'] = package
601 if 'and_dependencies' not in kwargs:
602 kwargs['and_dependencies'] = []
603 if 'python' not in kwargs['and_dependencies']:
604 kwargs['and_dependencies'].append('python')
605 super(PythonPackageDependency, self).__init__(**kwargs)
606 self.package = package
608 def _get_version(self):
609 package = self._get_package(self.package)
610 return self._get_version_from_package(package)
612 def _get_package(self, package):
614 return _importlib.import_module(package)
615 except ImportError as e:
616 raise DependencyError(
618 message="could not import the '{0}' package for {1}".format(
619 package, self.full_name()),
622 def _get_version_from_package(self, package):
624 version = package.__version__
625 except AttributeError:
630 for package,name,long_name,minimum_version,and_dependencies in [
631 ('nose', None, 'Nose Python package',
632 CHECKER['nosetests'].minimum_version, None),
633 ('jinja2', 'jinja', 'Jinja', (2, 6), None),
634 ('zmq', 'pyzmq', 'PyZMQ', (2, 1, 4), None),
635 ('IPython', None, 'IPython Python package',
636 CHECKER['ipython'].minimum_version, ['jinja', 'tornado', 'pyzmq']),
637 ('argparse', None, 'Argparse', None, None),
638 ('numpy', None, 'NumPy', None, None),
639 ('scipy', None, 'SciPy', None, None),
640 ('matplotlib', None, 'Matplotlib', None, None),
641 ('pandas', None, 'Pandas', (0, 8), None),
642 ('sympy', None, 'SymPy', None, None),
643 ('Cython', None, None, None, None),
644 ('networkx', None, 'NetworkX', None, None),
645 ('mayavi.mlab', None, 'MayaVi', None, None),
646 ('setuptools', None, 'Setuptools', None, None),
654 kwargs['and_dependencies'] = and_dependencies
655 CHECKER[name] = PythonPackageDependency(
656 package=package, name=name, long_name=long_name,
657 minimum_version=minimum_version, **kwargs)
659 del package, name, long_name, minimum_version, and_dependencies, kwargs
662 class MercurialPythonPackage (PythonPackageDependency):
663 def _get_version(self):
664 try: # mercurial >= 1.2
665 package = _importlib.import_module('mercurial.util')
666 except ImportError as e: # mercurial <= 1.1.2
667 package = self._get_package('mercurial.version')
668 return package.get_version()
670 return package.version()
673 CHECKER['mercurial'] = MercurialPythonPackage(
674 package='mercurial.util', name='mercurial',
675 long_name='Mercurial Python package',
676 minimum_version=CHECKER['hg'].minimum_version)
679 class TornadoPythonPackage (PythonPackageDependency):
680 def _get_version_from_package(self, package):
681 return package.version
683 def _get_parsed_version(self):
684 package = self._get_package(self.package)
685 return package.version_info
688 CHECKER['tornado'] = TornadoPythonPackage(
689 package='tornado', name='tornado', long_name='Tornado', minimum_version=(2, 0))
692 class SQLitePythonPackage (PythonPackageDependency):
693 def _get_version_from_package(self, package):
696 def _get_parsed_version(self):
697 return _sys.version_info
700 CHECKER['sqlite3-python'] = SQLitePythonPackage(
701 package='sqlite3', name='sqlite3-python',
702 long_name='SQLite Python package',
703 minimum_version=CHECKER['sqlite3'].minimum_version)
706 class UserTaskDependency (Dependency):
707 "Prompt the user to complete a task and check for success"
708 def __init__(self, prompt, **kwargs):
709 super(UserTaskDependency, self).__init__(**kwargs)
713 if _sys.version_info >= (3, ):
714 result = input(self.prompt)
716 result = raw_input(self.prompt)
717 return self._check_result(result)
719 def _check_result(self, result):
720 raise NotImplementedError()
723 class EditorTaskDependency (UserTaskDependency):
724 def __init__(self, **kwargs):
725 self.path = _os.path.expanduser(_os.path.join(
726 '~', 'swc-installation-test.txt'))
727 self.contents = 'Hello, world!'
728 super(EditorTaskDependency, self).__init__(
730 'Open your favorite text editor and create the file\n'
732 'containing the line:\n'
734 'Press enter here after you have done this.\n'
735 'You may remove the file after you have finished testing.'
736 ).format(self.path, self.contents),
739 def _check_result(self, result):
742 with open(self.path, 'r') as f:
745 raise DependencyError(
747 message='could not open {0!r}: {1}'.format(self.path, e)
749 if contents.strip() != self.contents:
750 raise DependencyError(
753 'file contents ({0!r}) did not match the expected {1!r}'
754 ).format(contents, self.contents))
757 CHECKER['other-editor'] = EditorTaskDependency(
758 name='other-editor', long_name='')
761 class VirtualDependency (Dependency):
763 return '{0} {1}'.format(
764 self.or_pass['dependency'].full_name(),
765 self.or_pass['version'])
768 for name,long_name,dependencies in [
769 ('virtual-shell', 'command line shell', (
779 ('virtual-editor', 'text/code editor', (
791 'other-editor', # last because it requires user interaction
793 ('virtual-browser', 'web browser', (
799 ('virtual-pypi-installer', 'PyPI installer', (
804 CHECKER[name] = VirtualDependency(
805 name=name, long_name=long_name, or_dependencies=dependencies)
806 del name, long_name, dependencies # cleanup namespace
809 def _print_info(key, value, indent=19):
810 print('{0}{1}: {2}'.format(key, ' '*(indent-len(key)), value))
812 def print_system_info():
813 print("If you do not understand why the above failures occurred,")
814 print("copy and send the *entire* output (all info above and summary")
815 print("below) to the instructor for help.")
817 print('==================')
818 print('System information')
819 print('==================')
820 _print_info('os.name', _os.name)
821 _print_info('os.uname', _platform.uname())
822 _print_info('platform', _sys.platform)
823 _print_info('platform+', _platform.platform())
825 'linux_distribution',
829 value = getattr(_platform, pversion)()
831 _print_info(pversion, value)
832 _print_info('prefix', _sys.prefix)
833 _print_info('exec_prefix', _sys.exec_prefix)
834 _print_info('executable', _sys.executable)
835 _print_info('version_info', _sys.version_info)
836 _print_info('version', _sys.version)
837 _print_info('environment', '')
838 for key,value in sorted(_os.environ.items()):
839 print(' {0}={1}'.format(key, value))
840 print('==================')
842 def print_suggestions(instructor_fallback=True):
844 print('For suggestions on installing missing packages, see')
845 print('http://software-carpentry.org/setup/')
847 print('For instructings on installing a particular package,')
848 print('see the failure message for that package printed above.')
849 if instructor_fallback:
851 print('For help, email the *entire* output of this script to')
852 print('your instructor.')
855 if __name__ == '__main__':
856 import optparse as _optparse
858 parser = _optparse.OptionParser(usage='%prog [options] [check...]')
860 parser.format_epilog = lambda formatter: '\n' + epilog
862 '-v', '--verbose', action='store_true',
863 help=('print additional information to help troubleshoot '
864 'installation issues'))
865 options,args = parser.parse_args()
868 except InvalidCheck as e:
869 print("I don't know how to check for {0!r}".format(e.check))
870 print('I do know how to check for:')
871 for key,checker in sorted(CHECKER.items()):
872 if checker.long_name != checker.name:
873 print(' {0} {1}({2})'.format(
874 key, ' '*(20-len(key)), checker.long_name))
876 print(' {0}'.format(key))
882 print_suggestions(instructor_fallback=True)