From a3144899f93c16b92064b92a5ec410dc54553160 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Wed, 17 Dec 2014 20:04:38 -0800 Subject: [PATCH 1/7] README.md: Change headers ("Students" -> "For leaners") This is based on swcarpentry/workshop-template@5451fb9c1 (Enumerating setup/run instructions for installation test scripts, 2014-12-17). Greg suggested he switch from "Students" to "Learners" because some professors attend workshops as students but don't feel comfortable being called students [1]? Or something like that. I don't mind either way, so we'll just go with Greg's preference. The addition of a "For" in the section headings seems like a good change though, since it makes it clear that the noun in the heading marks the intended audience for that section, not the intended subject. I went with 'Sentence case' titles because I think they're more readable and it's easier to be consistent. There are a number of folks who agree with me [2,3], including (apparently) the Oxford Guide to Style [4] (I'm ok with title case for book and organization titles ;). Although the American Psychological Association perfers 'Title Case' for the first two levels of headings as well [5]. [1]: https://github.com/swcarpentry/workshop-template/pull/89#issuecomment-67718126 [2]: http://www.snappysentences.com/sentence-case-v-title-case/ [3]: http://www.stickycontent.com/blog/are-you-team-title-case-or-team-sentence-case.php [4]: http://ux.stackexchange.com/a/48620 [5]: http://blog.apastyle.org/apastyle/2012/03/title-case-and-sentence-case-capitalization-in-apa-style.html Based-on-patch-by: Greg Wilson --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ad118cc..35ff0d2 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -Students -======== +For learners +============ This directory contains scripts for testing your machine to make sure you have the software you'll need for your workshop installed. See @@ -45,8 +45,8 @@ option: os.name : posix … -Instructors -=========== +For instructors +=============== `swc-installation-test-1.py` is pretty simple, and just checks that the students have a recent enough version of Python installed that From 3ffcd7155d7014cd9555fa2131852079c9cfc8b9 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 24 Jan 2015 11:03:06 -0800 Subject: [PATCH 2/7] swc-installation-test-2.py: Collect classes together Instead of interleaving class definitions with their instantiation. This will make it easier to build dependency chains that mix a few classes, because we don't have to worry about whether or not that class has been defined yet. --- swc-installation-test-2.py | 412 ++++++++++++++++++------------------- 1 file changed, 206 insertions(+), 206 deletions(-) diff --git a/swc-installation-test-2.py b/swc-installation-test-2.py index 9898d1b..5d794c7 100755 --- a/swc-installation-test-2.py +++ b/swc-installation-test-2.py @@ -378,21 +378,11 @@ class Dependency (object): return tuple(parsed_version) -class PythonDependency (Dependency): - def __init__(self, name='python', long_name='Python version', - minimum_version=(2, 6), **kwargs): - super(PythonDependency, self).__init__( - name=name, long_name=long_name, minimum_version=minimum_version, - **kwargs) - - def _get_version(self): - return _sys.version - - def _get_parsed_version(self): - return _sys.version_info - - -CHECKER['python'] = PythonDependency() +class VirtualDependency (Dependency): + def _check(self): + return '{0} {1}'.format( + self.or_pass['dependency'].full_name(), + self.or_pass['version']) class CommandDependency (Dependency): @@ -487,54 +477,84 @@ class CommandDependency (Dependency): return match.group(1) -def _program_files_paths(*args): - "Utility for generating MS Windows search paths" - pf = _os.environ.get('ProgramFiles', '/usr/bin') - pfx86 = _os.environ.get('ProgramFiles(x86)', pf) - paths = [_os.path.join(pf, *args)] - if pfx86 != pf: - paths.append(_os.path.join(pfx86, *args)) - return paths +class PathCommandDependency (CommandDependency): + """A command that doesn't support --version or equivalent options + On some operating systems (e.g. OS X), a command's executable may + be hard to find, or not exist in the PATH. Work around that by + just checking for the existence of a characteristic file or + directory. Since the characteristic path may depend on OS, + installed version, etc., take a list of paths, and succeed if any + of them exists. + """ + def _get_command_version_stream(self, *args, **kwargs): + raise NotImplementedError() -for command,long_name,minimum_version,paths in [ - ('sh', 'Bourne Shell', None, None), - ('ash', 'Almquist Shell', None, None), - ('bash', 'Bourne Again Shell', None, None), - ('csh', 'C Shell', None, None), - ('ksh', 'KornShell', None, None), - ('dash', 'Debian Almquist Shell', None, None), - ('tcsh', 'TENEX C Shell', None, None), - ('zsh', 'Z Shell', None, None), - ('git', 'Git', (1, 7, 0), None), - ('hg', 'Mercurial', (2, 0, 0), None), - ('EasyMercurial', None, (1, 3), None), - ('pip', None, None, None), - ('sqlite3', 'SQLite 3', None, None), - ('nosetests', 'Nose', (1, 0, 0), None), - ('ipython', 'IPython script', (1, 0), None), - ('emacs', 'Emacs', None, None), - ('xemacs', 'XEmacs', None, None), - ('vim', 'Vim', None, None), - ('vi', None, None, None), - ('nano', 'Nano', None, None), - ('gedit', None, None, None), - ('kate', 'Kate', None, None), - ('notepad++', 'Notepad++', None, - _program_files_paths('Notepad++', 'notepad++.exe')), - ('firefox', 'Firefox', None, - _program_files_paths('Mozilla Firefox', 'firefox.exe')), - ('google-chrome', 'Google Chrome', None, - _program_files_paths('Google', 'Chrome', 'Application', 'chrome.exe') - ), - ('chromium', 'Chromium', None, None), - ]: - if not long_name: - long_name = command - CHECKER[command] = CommandDependency( - command=command, paths=paths, long_name=long_name, - minimum_version=minimum_version) -del command, long_name, minimum_version, paths # cleanup namespace + def _get_version_stream(self, *args, **kwargs): + raise NotImplementedError() + + def _get_version(self): + for path in self.paths: + if _os.path.exists(path): + return None + raise DependencyError( + checker=self, + message=( + 'nothing exists at any of the expected paths for {0}:\n {1}' + ).format( + self.full_name(), + '\n '.join(p for p in self.paths))) + + +class UserTaskDependency (Dependency): + "Prompt the user to complete a task and check for success" + def __init__(self, prompt, **kwargs): + super(UserTaskDependency, self).__init__(**kwargs) + self.prompt = prompt + + def _check(self): + if _sys.version_info >= (3, ): + result = input(self.prompt) + else: # Python 2.x + result = raw_input(self.prompt) + return self._check_result(result) + + def _check_result(self, result): + raise NotImplementedError() + + +class EditorTaskDependency (UserTaskDependency): + def __init__(self, **kwargs): + self.path = _os.path.expanduser(_os.path.join( + '~', 'swc-installation-test.txt')) + self.contents = 'Hello, world!' + super(EditorTaskDependency, self).__init__( + prompt=( + 'Open your favorite text editor and create the file\n' + ' {0}\n' + 'containing the line:\n' + ' {1}\n' + 'Press enter here after you have done this.\n' + 'You may remove the file after you have finished testing.' + ).format(self.path, self.contents), + **kwargs) + + def _check_result(self, result): + message = None + try: + with open(self.path, 'r') as f: + contents = f.read() + except IOError as e: + raise DependencyError( + checker=self, + message='could not open {0!r}: {1}'.format(self.path, e) + )# from e + if contents.strip() != self.contents: + raise DependencyError( + checker=self, + message=( + 'file contents ({0!r}) did not match the expected {1!r}' + ).format(contents, self.contents)) class MakeDependency (CommandDependency): @@ -569,9 +589,6 @@ class MakeDependency (CommandDependency): self.version_options = version_options -CHECKER['make'] = MakeDependency(command='make', minimum_version=None) - - class EasyInstallDependency (CommandDependency): def _get_version(self): try: @@ -587,65 +604,18 @@ class EasyInstallDependency (CommandDependency): self.version_stream = version_stream -CHECKER['easy_install'] = EasyInstallDependency( - command='easy_install', long_name='Setuptools easy_install', - minimum_version=None) - - -CHECKER['py.test'] = CommandDependency( - command='py.test', version_stream='stderr', - minimum_version=None) - - -class PathCommandDependency (CommandDependency): - """A command that doesn't support --version or equivalent options - - On some operating systems (e.g. OS X), a command's executable may - be hard to find, or not exist in the PATH. Work around that by - just checking for the existence of a characteristic file or - directory. Since the characteristic path may depend on OS, - installed version, etc., take a list of paths, and succeed if any - of them exists. - """ - def _get_command_version_stream(self, *args, **kwargs): - raise NotImplementedError() - - def _get_version_stream(self, *args, **kwargs): - raise NotImplementedError() +class PythonDependency (Dependency): + def __init__(self, name='python', long_name='Python version', + minimum_version=(2, 6), **kwargs): + super(PythonDependency, self).__init__( + name=name, long_name=long_name, minimum_version=minimum_version, + **kwargs) def _get_version(self): - for path in self.paths: - if _os.path.exists(path): - return None - raise DependencyError( - checker=self, - message=( - 'nothing exists at any of the expected paths for {0}:\n {1}' - ).format( - self.full_name(), - '\n '.join(p for p in self.paths))) - + return _sys.version -for paths,name,long_name in [ - ([_os.path.join(_ROOT_PATH, 'Applications', 'Sublime Text 2.app')], - 'sublime-text', 'Sublime Text'), - ([_os.path.join(_ROOT_PATH, 'Applications', 'TextMate.app')], - 'textmate', 'TextMate'), - ([_os.path.join(_ROOT_PATH, 'Applications', 'TextWrangler.app')], - 'textwrangler', 'TextWrangler'), - ([_os.path.join(_ROOT_PATH, 'Applications', 'Safari.app')], - 'safari', 'Safari'), - ([_os.path.join(_ROOT_PATH, 'Applications', 'Xcode.app'), # OS X >=1.7 - _os.path.join(_ROOT_PATH, 'Developer', 'Applications', 'Xcode.app' - ) # OS X 1.6, - ], - 'xcode', 'Xcode'), - ]: - if not long_name: - long_name = name - CHECKER[name] = PathCommandDependency( - command=None, paths=paths, name=name, long_name=long_name) -del paths, name, long_name # cleanup namespace + def _get_parsed_version(self): + return _sys.version_info class PythonPackageDependency (Dependency): @@ -681,6 +651,122 @@ class PythonPackageDependency (Dependency): return version +class MercurialPythonPackage (PythonPackageDependency): + def _get_version(self): + try: # mercurial >= 1.2 + package = _importlib.import_module('mercurial.util') + except ImportError as e: # mercurial <= 1.1.2 + package = self._get_package('mercurial.version') + return package.get_version() + else: + return package.version() + + +class TornadoPythonPackage (PythonPackageDependency): + def _get_version_from_package(self, package): + return package.version + + def _get_parsed_version(self): + package = self._get_package(self.package) + return package.version_info + + +class SQLitePythonPackage (PythonPackageDependency): + def _get_version_from_package(self, package): + return _sys.version + + def _get_parsed_version(self): + return _sys.version_info + + +def _program_files_paths(*args): + "Utility for generating MS Windows search paths" + pf = _os.environ.get('ProgramFiles', '/usr/bin') + pfx86 = _os.environ.get('ProgramFiles(x86)', pf) + paths = [_os.path.join(pf, *args)] + if pfx86 != pf: + paths.append(_os.path.join(pfx86, *args)) + return paths + + +CHECKER['python'] = PythonDependency() + + +for command,long_name,minimum_version,paths in [ + ('sh', 'Bourne Shell', None, None), + ('ash', 'Almquist Shell', None, None), + ('bash', 'Bourne Again Shell', None, None), + ('csh', 'C Shell', None, None), + ('ksh', 'KornShell', None, None), + ('dash', 'Debian Almquist Shell', None, None), + ('tcsh', 'TENEX C Shell', None, None), + ('zsh', 'Z Shell', None, None), + ('git', 'Git', (1, 7, 0), None), + ('hg', 'Mercurial', (2, 0, 0), None), + ('EasyMercurial', None, (1, 3), None), + ('pip', None, None, None), + ('sqlite3', 'SQLite 3', None, None), + ('nosetests', 'Nose', (1, 0, 0), None), + ('ipython', 'IPython script', (1, 0), None), + ('emacs', 'Emacs', None, None), + ('xemacs', 'XEmacs', None, None), + ('vim', 'Vim', None, None), + ('vi', None, None, None), + ('nano', 'Nano', None, None), + ('gedit', None, None, None), + ('kate', 'Kate', None, None), + ('notepad++', 'Notepad++', None, + _program_files_paths('Notepad++', 'notepad++.exe')), + ('firefox', 'Firefox', None, + _program_files_paths('Mozilla Firefox', 'firefox.exe')), + ('google-chrome', 'Google Chrome', None, + _program_files_paths('Google', 'Chrome', 'Application', 'chrome.exe') + ), + ('chromium', 'Chromium', None, None), + ]: + if not long_name: + long_name = command + CHECKER[command] = CommandDependency( + command=command, paths=paths, long_name=long_name, + minimum_version=minimum_version) +del command, long_name, minimum_version, paths # cleanup namespace + + +CHECKER['make'] = MakeDependency(command='make', minimum_version=None) + + +CHECKER['easy_install'] = EasyInstallDependency( + command='easy_install', long_name='Setuptools easy_install', + minimum_version=None) + + +CHECKER['py.test'] = CommandDependency( + command='py.test', version_stream='stderr', + minimum_version=None) + + +for paths,name,long_name in [ + ([_os.path.join(_ROOT_PATH, 'Applications', 'Sublime Text 2.app')], + 'sublime-text', 'Sublime Text'), + ([_os.path.join(_ROOT_PATH, 'Applications', 'TextMate.app')], + 'textmate', 'TextMate'), + ([_os.path.join(_ROOT_PATH, 'Applications', 'TextWrangler.app')], + 'textwrangler', 'TextWrangler'), + ([_os.path.join(_ROOT_PATH, 'Applications', 'Safari.app')], + 'safari', 'Safari'), + ([_os.path.join(_ROOT_PATH, 'Applications', 'Xcode.app'), # OS X >=1.7 + _os.path.join(_ROOT_PATH, 'Developer', 'Applications', 'Xcode.app' + ) # OS X 1.6, + ], + 'xcode', 'Xcode'), + ]: + if not long_name: + long_name = name + CHECKER[name] = PathCommandDependency( + command=None, paths=paths, name=name, long_name=long_name) +del paths, name, long_name # cleanup namespace + + for package,name,long_name,minimum_version,and_dependencies in [ ('nose', None, 'Nose Python package', CHECKER['nosetests'].minimum_version, None), @@ -715,112 +801,26 @@ for package,name,long_name,minimum_version,and_dependencies in [ del package, name, long_name, minimum_version, and_dependencies, kwargs -class MercurialPythonPackage (PythonPackageDependency): - def _get_version(self): - try: # mercurial >= 1.2 - package = _importlib.import_module('mercurial.util') - except ImportError as e: # mercurial <= 1.1.2 - package = self._get_package('mercurial.version') - return package.get_version() - else: - return package.version() - - CHECKER['mercurial'] = MercurialPythonPackage( package='mercurial.util', name='mercurial', long_name='Mercurial Python package', minimum_version=CHECKER['hg'].minimum_version) -class TornadoPythonPackage (PythonPackageDependency): - def _get_version_from_package(self, package): - return package.version - - def _get_parsed_version(self): - package = self._get_package(self.package) - return package.version_info - - CHECKER['tornado'] = TornadoPythonPackage( package='tornado', name='tornado', long_name='Tornado', minimum_version=(2, 0)) -class SQLitePythonPackage (PythonPackageDependency): - def _get_version_from_package(self, package): - return _sys.version - - def _get_parsed_version(self): - return _sys.version_info - - CHECKER['sqlite3-python'] = SQLitePythonPackage( package='sqlite3', name='sqlite3-python', long_name='SQLite Python package', minimum_version=CHECKER['sqlite3'].minimum_version) -class UserTaskDependency (Dependency): - "Prompt the user to complete a task and check for success" - def __init__(self, prompt, **kwargs): - super(UserTaskDependency, self).__init__(**kwargs) - self.prompt = prompt - - def _check(self): - if _sys.version_info >= (3, ): - result = input(self.prompt) - else: # Python 2.x - result = raw_input(self.prompt) - return self._check_result(result) - - def _check_result(self, result): - raise NotImplementedError() - - -class EditorTaskDependency (UserTaskDependency): - def __init__(self, **kwargs): - self.path = _os.path.expanduser(_os.path.join( - '~', 'swc-installation-test.txt')) - self.contents = 'Hello, world!' - super(EditorTaskDependency, self).__init__( - prompt=( - 'Open your favorite text editor and create the file\n' - ' {0}\n' - 'containing the line:\n' - ' {1}\n' - 'Press enter here after you have done this.\n' - 'You may remove the file after you have finished testing.' - ).format(self.path, self.contents), - **kwargs) - - def _check_result(self, result): - message = None - try: - with open(self.path, 'r') as f: - contents = f.read() - except IOError as e: - raise DependencyError( - checker=self, - message='could not open {0!r}: {1}'.format(self.path, e) - )# from e - if contents.strip() != self.contents: - raise DependencyError( - checker=self, - message=( - 'file contents ({0!r}) did not match the expected {1!r}' - ).format(contents, self.contents)) - - CHECKER['other-editor'] = EditorTaskDependency( name='other-editor', long_name='') -class VirtualDependency (Dependency): - def _check(self): - return '{0} {1}'.format( - self.or_pass['dependency'].full_name(), - self.or_pass['version']) - - for name,long_name,dependencies in [ ('virtual-shell', 'command line shell', ( 'bash', From d13b26c9fb89beebe9ff9e9dfc86ba8ddb8d01cd Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 24 Jan 2015 11:05:13 -0800 Subject: [PATCH 3/7] swc-installation-test-2.py: Add minimum browser requirements for IPython MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit IPython notebooks (at least for version 1.2.1 [1], 2.3.0 [2], and 3.0.0 [3]) have the following minimum browser requirements: Chrome ≥ 13 Safari ≥ 5 Firefox ≥ 6 Due to the notebook's usage of WebSockets and the flexible box model [1,2,3]. This commit ensures that folks requiring IPython have a notebook-compatible browser. We don't actually check the Safari version, because I'm not sure what the appropriate command-line switch is to get it to print version information. [1]: http://ipython.org/ipython-doc/1/install/install.html#browser-compatibility [2]: http://ipython.org/ipython-doc/2/install/install.html#browser-compatibility [3]: http://ipython.org/ipython-doc/dev/install/install.html#browser-compatibility --- swc-installation-test-2.py | 43 +++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/swc-installation-test-2.py b/swc-installation-test-2.py index 5d794c7..16532ff 100755 --- a/swc-installation-test-2.py +++ b/swc-installation-test-2.py @@ -775,7 +775,48 @@ for package,name,long_name,minimum_version,and_dependencies in [ ('jinja2', 'jinja', 'Jinja', (2, 6), None), ('zmq', 'pyzmq', 'PyZMQ', (2, 1, 4), None), ('IPython', None, 'IPython Python package', - CHECKER['ipython'].minimum_version, ['jinja', 'tornado', 'pyzmq']), + CHECKER['ipython'].minimum_version, [ + 'jinja', + 'tornado', + 'pyzmq', + VirtualDependency( + name='virtual-browser-ipython', + long_name='IPython-compatible web browser', + or_dependencies=[ + CommandDependency( + command=CHECKER['firefox'].command, + paths=CHECKER['firefox'].paths, + name='{0}-for-ipython'.format( + CHECKER['firefox'].name), + long_name='{0} for IPython'.format( + CHECKER['firefox'].long_name), + minimum_version=(6, 0)), + CommandDependency( + command=CHECKER['google-chrome'].command, + paths=CHECKER['google-chrome'].paths, + name='{0}-for-ipython'.format( + CHECKER['google-chrome'].name), + long_name='{0} for IPython'.format( + CHECKER['google-chrome'].long_name), + minimum_version=(13, 0)), + CommandDependency( + command=CHECKER['chromium'].command, + paths=CHECKER['chromium'].paths, + name='{0}-for-ipython'.format( + CHECKER['chromium'].name), + long_name='{0} for IPython'.format( + CHECKER['chromium'].long_name), + minimum_version=(13, 0)), + PathCommandDependency( + command=CHECKER['safari'].command, + paths=CHECKER['safari'].paths, + name='{0}-for-ipython'.format( + CHECKER['safari'].name), + long_name='{0} for IPython'.format( + CHECKER['safari'].long_name), + minimum_version=(5, 0)), + ]), + ]), ('argparse', None, 'Argparse', None, None), ('numpy', None, 'NumPy', None, None), ('scipy', None, 'SciPy', None, None), From 14156218b0013d670a82783e33b14e5a15d1b252 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 25 Jan 2015 15:32:14 -0800 Subject: [PATCH 4/7] swc-installation-test-2.py: Convert PathCommandDependency to VersionPlistCommandDependency Philip Guo suggested using version.plist to lookup the version number for applications on OS X [1]. Searching around, I found an example of Safari's version.plist posted by ASmit010 [2]: BuildVersion 1 CFBundleShortVersionString 5.1.7 CFBundleVersion 7534.57.2 ProjectName WebBrowser SourceVersion 7534057002000000 Looking at that example, the version information we want is associated with CFBundleShortVersionString [3]: CFBundleShortVersionString (String - iOS, OS X) specifies the release version number of the bundle, which identifies a released iteration of the app. The release version number is a string comprised of three period-separated integers. The first integer represents major revisions to the app, such as revisions that implement new features or major changes. The second integer denotes revisions that implement less prominent features. The third integer represents maintenance releases. The value for this key differs from the value for CFBundleVersion, which identifies an iteration (released or unreleased) of the app. This key can be localized by including it in your InfoPlist.strings files The sibling-entries for keys and values are a bit awkward using Python's stock ElementTree [4,5], which doesn't have built-in methods for finding parents or siblings [6]. I've followed the example set by lxml and added _get_parent and _get_next helpers (mirroring getparent [7] and getnext [8]) to make it the logic in _get_version_from_plist more clear. [1]: https://twitter.com/pgbovine/status/559094439009075203 [2]: https://discussions.apple.com/message/19757845#19757845 [3]: https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/20001431-111349 [4]: https://docs.python.org/2/library/xml.etree.elementtree.html [5]: https://docs.python.org/3/library/xml.etree.elementtree.html [6]: http://lxml.de/1.3/api.html#trees-and-documents [7]: http://lxml.de/api/lxml.etree._Element-class.html#getparent [8]: http://lxml.de/api/lxml.etree._Element-class.html#getnext --- swc-installation-test-2.py | 77 +++++++++++++++++++++++++++++--------- 1 file changed, 60 insertions(+), 17 deletions(-) diff --git a/swc-installation-test-2.py b/swc-installation-test-2.py index 16532ff..54e5250 100755 --- a/swc-installation-test-2.py +++ b/swc-installation-test-2.py @@ -50,6 +50,7 @@ try: # Python 3.x import urllib.parse as _urllib_parse except ImportError: # Python 2.x import urllib as _urllib_parse # for quote() +import xml.etree.ElementTree as _element_tree if not hasattr(_shlex, 'quote'): # Python versions older than 3.3 @@ -477,26 +478,62 @@ class CommandDependency (Dependency): return match.group(1) -class PathCommandDependency (CommandDependency): +class VersionPlistCommandDependency (CommandDependency): """A command that doesn't support --version or equivalent options - On some operating systems (e.g. OS X), a command's executable may - be hard to find, or not exist in the PATH. Work around that by - just checking for the existence of a characteristic file or - directory. Since the characteristic path may depend on OS, - installed version, etc., take a list of paths, and succeed if any - of them exists. + On OS X, a command's executable may be hard to find, or not exist + in the PATH. Work around that by looking up the version + information in the package's version.plist file. """ + def __init__(self, key='CFBundleShortVersionString', **kwargs): + super(VersionPlistCommandDependency, self).__init__(**kwargs) + self.key = key + def _get_command_version_stream(self, *args, **kwargs): raise NotImplementedError() def _get_version_stream(self, *args, **kwargs): raise NotImplementedError() + @staticmethod + def _get_parent(root, element): + """Returns the parent of this element or None for the root element + """ + for node in root.iter(): + if element in node: + return node + raise ValueError((root, element)) + + @staticmethod + def _get_next(root, element): + """Returns the following sibling of this element or None + """ + parent = self._get_parent(root=root, element=element) + siblings = iter(parent) + for node in siblings: + if node == element: + try: + return next(siblings) + except StopIteration: + return None + return None + + def _get_version_from_plist(self, path): + """Parse the plist and return the value string for self.key + """ + tree = _element_tree.parse(source=path) + data = {} + for key in tree.findall('.//key'): + value = self._get_next(root=tree, element=key) + if value.tag != 'string': + raise ValueError((tree, key, value)) + data[key.text] = value.text + return data[self.key] + def _get_version(self): for path in self.paths: if _os.path.exists(path): - return None + return self._get_version_from_plist(path=path) raise DependencyError( checker=self, message=( @@ -746,23 +783,28 @@ CHECKER['py.test'] = CommandDependency( for paths,name,long_name in [ - ([_os.path.join(_ROOT_PATH, 'Applications', 'Sublime Text 2.app')], + ([_os.path.join(_ROOT_PATH, 'Applications', 'Sublime Text 2.app', + 'Contents', 'version.plist')], 'sublime-text', 'Sublime Text'), - ([_os.path.join(_ROOT_PATH, 'Applications', 'TextMate.app')], + ([_os.path.join(_ROOT_PATH, 'Applications', 'TextMate.app', + 'Contents', 'version.plist')], 'textmate', 'TextMate'), - ([_os.path.join(_ROOT_PATH, 'Applications', 'TextWrangler.app')], + ([_os.path.join(_ROOT_PATH, 'Applications', 'TextWrangler.app', + 'Contents', 'version.plist')], 'textwrangler', 'TextWrangler'), - ([_os.path.join(_ROOT_PATH, 'Applications', 'Safari.app')], + ([_os.path.join(_ROOT_PATH, 'Applications', 'Safari.app', + 'Contents', 'version.plist')], 'safari', 'Safari'), - ([_os.path.join(_ROOT_PATH, 'Applications', 'Xcode.app'), # OS X >=1.7 - _os.path.join(_ROOT_PATH, 'Developer', 'Applications', 'Xcode.app' - ) # OS X 1.6, + ([_os.path.join(_ROOT_PATH, 'Applications', 'Xcode.app', + 'Contents', 'version.plist'), # OS X >=1.7 + _os.path.join(_ROOT_PATH, 'Developer', 'Applications', 'Xcode.app', + 'Contents', 'version.plist'), # OS X 1.6, ], 'xcode', 'Xcode'), ]: if not long_name: long_name = name - CHECKER[name] = PathCommandDependency( + CHECKER[name] = VersionPlistCommandDependency( command=None, paths=paths, name=name, long_name=long_name) del paths, name, long_name # cleanup namespace @@ -807,9 +849,10 @@ for package,name,long_name,minimum_version,and_dependencies in [ long_name='{0} for IPython'.format( CHECKER['chromium'].long_name), minimum_version=(13, 0)), - PathCommandDependency( + VersionPlistCommandDependency( command=CHECKER['safari'].command, paths=CHECKER['safari'].paths, + key=CHECKER['safari'].key, name='{0}-for-ipython'.format( CHECKER['safari'].name), long_name='{0} for IPython'.format( From 9bffbc22f007bd7c88e1a246043983fcf53a6528 Mon Sep 17 00:00:00 2001 From: Andrew Walker Date: Mon, 26 Jan 2015 18:52:04 +0000 Subject: [PATCH 5/7] Fix decorator in VersionPlistCommandDependency This class includes a static method that calls a static method and this fails with: $ ./swc-installation-test-2.py safari check Safari (safari)... Traceback (most recent call last): File "./swc-installation-test-2.py", line 1007, in passed = check(args) File "./swc-installation-test-2.py", line 243, in check version = checker.check() File "./swc-installation-test-2.py", line 298, in check return self._check() File "./swc-installation-test-2.py", line 340, in _check version = self._get_version() File "./swc-installation-test-2.py", line 536, in _get_version return self._get_version_from_plist(path=path) File "./swc-installation-test-2.py", line 527, in _get_version_from_plist value = self._get_next(root=tree, element=key) File "./swc-installation-test-2.py", line 511, in _get_next parent = self._get_parent(root=root, element=element) NameError: global name 'self' is not defined Change the static method _get_next (which is not passed self when called) to a class method (which is passed the class object) so that we can call the _get_parent static method (whatever the class is called). --- swc-installation-test-2.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/swc-installation-test-2.py b/swc-installation-test-2.py index 54e5250..053bab6 100755 --- a/swc-installation-test-2.py +++ b/swc-installation-test-2.py @@ -504,11 +504,11 @@ class VersionPlistCommandDependency (CommandDependency): return node raise ValueError((root, element)) - @staticmethod - def _get_next(root, element): + @classmethod + def _get_next(cls, root, element): """Returns the following sibling of this element or None """ - parent = self._get_parent(root=root, element=element) + parent = cls._get_parent(root=root, element=element) siblings = iter(parent) for node in siblings: if node == element: From 2c1913d1a6be11eafacbbe5e178418c7953d57a8 Mon Sep 17 00:00:00 2001 From: yash Date: Sat, 14 Mar 2015 02:20:39 +0530 Subject: [PATCH 6/7] swc-installation-test-2.py: Comment explaining implemention --- swc-installation-test-2.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/swc-installation-test-2.py b/swc-installation-test-2.py index 053bab6..74d44c8 100755 --- a/swc-installation-test-2.py +++ b/swc-installation-test-2.py @@ -22,6 +22,25 @@ version of a particular dependency, and you just want to re-test that dependency. """ +# Some details about the implementation: + +# The dependencies are divided into a hierarchy of classes rooted on +# Dependency class. You can refer to the code to see which package +# comes under which type of dependency. + +# The CHECKER dictionary stores information about all the dependencies +# and CHECKS stores list of the dependencies which are to be checked in +# the current workshop. + +# In the "__name__ == '__main__'" block, we launch all the checks with +# check() function, which prints information about the tests as they run +# and details about the failures after the tests complete. In case of +# failure, the functions print_system_info() and print_suggestions() +# are called after this, where the former prints information about the +# user's system for debugging purposes while the latter prints some +# suggestions to follow. + + from __future__ import print_function # for Python 2.6 compatibility import distutils.ccompiler as _distutils_ccompiler From 74844ea43e4df102d9013eae92d19f7f924ee00b Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 9 Nov 2015 09:46:21 -0800 Subject: [PATCH 7/7] swc-installation-test-2.py: Disable sympy, Cython, networkx, and mayavi.mlab These are rarely used in SWC workshops, and many instructors are pointing their students at these installation-test scripts without customizing CHECKS first. Until we get an easier way to configure the checks, just make the default settings a bit more relaxed. --- swc-installation-test-2.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/swc-installation-test-2.py b/swc-installation-test-2.py index 74d44c8..bd7960d 100755 --- a/swc-installation-test-2.py +++ b/swc-installation-test-2.py @@ -115,10 +115,10 @@ CHECKS = [ 'scipy', 'matplotlib', 'pandas', - 'sympy', - 'Cython', - 'networkx', - 'mayavi.mlab', + #'sympy', + #'Cython', + #'networkx', + #'mayavi.mlab', ] CHECKER = {}