grim/pyscovery

Parents 231ec5dc3d81
Children c5caa795c094
added recursion support with unit tests to make sure deep recursion works too
--- a/src/pyplugin.py Sat Mar 23 01:39:49 2013 -0500
+++ b/src/pyplugin.py Sat Mar 23 03:37:41 2013 -0500
@@ -14,9 +14,13 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+import fnmatch
import importlib
import inspect
+import os
+# we use a list since it is a mutable sequence type. Meaning we can add to it
+# while iterating.
PATHS = []
def add_path(path):
@@ -49,7 +53,15 @@
return PATHS
-def find(cls):
+def clear_paths():
+ """
+ Clears all search paths
+ """
+
+ PATHS[:] = []
+
+
+def find(cls, recurse=False):
"""
Find all plugins that are subclasses of cls in the current search paths
"""
@@ -62,6 +74,9 @@
for path in PATHS:
mod = importlib.import_module(path)
+ if recurse and _is_package(mod):
+ _recurse(mod)
+
for symbol_name in dir(mod):
if symbol_name == cls_name:
continue
@@ -77,4 +92,42 @@
yield symbol
+def _is_package(module):
+ """
+ Returns true if the module's name is __init__, false otherwise
+ """
+
+ filename = os.path.basename(module.__file__)
+ base, _ = os.path.splitext(filename)
+
+ return base == '__init__'
+
+
+def _recurse(module):
+ """
+ Adds additional modules from a package
+ """
+
+ dirname = os.path.dirname(module.__file__)
+
+ make_module = lambda name: '.'.join((module.__name__, name))
+
+ for name in os.listdir(dirname):
+ base, _ = os.path.splitext(name)
+
+ if base == '__init__':
+ continue
+
+ filename = os.path.join(dirname, name)
+
+ if os.path.isdir(filename):
+ add_path(make_module(base))
+ elif os.path.isfile(filename):
+ if fnmatch.fnmatch(name, '*.py') or \
+ fnmatch.fnmatch(name, '*.py[co]'):
+ add_path(make_module(base))
+ else:
+ print name
+
+
__all__ = [add_path, remove_path, get_paths, find]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/modules/deep/__init__.py Sat Mar 23 03:37:41 2013 -0500
@@ -0,0 +1,28 @@
+# pyplugin - A python plugin finder
+# Copyright (C) 2013 Gary Kramlich <grim@reaperworld.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+This module contains a plugin and another module that contains another plugin
+to test deep recursion
+"""
+
+from tests import Plugin
+
+
+class DeepRecursion(Plugin): # pylint:disable-msg=R0903
+ """ a plugin """
+ pass
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/modules/deep/deeper.py Sat Mar 23 03:37:41 2013 -0500
@@ -0,0 +1,28 @@
+# pyplugin - A python plugin finder
+# Copyright (C) 2013 Gary Kramlich <grim@reaperworld.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+This module contains a plugin and another module that contains another plugin
+to test deep recursion
+"""
+
+from tests import Plugin
+
+
+class DeeperRecursion(Plugin): # pylint:disable-msg=R0903
+ """ a plugin """
+ pass
+
--- a/tests/tests.py Sat Mar 23 01:39:49 2013 -0500
+++ b/tests/tests.py Sat Mar 23 03:37:41 2013 -0500
@@ -26,6 +26,29 @@
PATH = 'test'
+def test_path(test, path, count, recurse=False):
+ """
+ Given a module path, test that we find count plugins
+ """
+
+ pyplugin.add_path(path)
+
+ gen = pyplugin.find(Plugin, recurse=recurse)
+ test.assertTrue(inspect.isgenerator(gen))
+
+ found = list(gen)
+ test.assertEqual(len(found), count, 'plugins found: {}'.format(found))
+
+
+def test_path_count(test, count):
+ """
+ Asserts if the number of paths doesn't match count
+ """
+
+ paths = pyplugin.get_paths()
+ test.assertEqual(len(paths), count, 'search paths {}'.format(paths))
+
+
class Plugin(object): # pylint:disable-msg=R0903
"""
The class all the test plugins descend from
@@ -39,24 +62,13 @@
Unit tests for path manipulation
"""
- def _test_path_count(self, count):
- """
- Modular form to check how many paths are in the search paths
- """
-
- paths = pyplugin.get_paths()
- self.assertEqual(len(paths), count,
- 'search paths {}'.format(paths))
-
-
def setUp(self): # pylint:disable-msg=C0103
- paths = pyplugin.get_paths()
- for path in paths:
- pyplugin.remove_path(path)
+ pyplugin.clear_paths()
def tearDown(self): # pylint:disable-msg=C0103
- self._test_path_count(0)
+ test_path_count(self, 0)
+ pyplugin.clear_paths()
def test_add_remove(self):
@@ -65,7 +77,7 @@
"""
pyplugin.add_path(PATH)
- self._test_path_count(1)
+ test_path_count(self, 1)
pyplugin.remove_path(PATH)
@@ -76,7 +88,7 @@
pyplugin.add_path(PATH)
pyplugin.add_path(PATH)
- self._test_path_count(1)
+ test_path_count(self, 1)
pyplugin.remove_path(PATH)
@@ -97,10 +109,10 @@
second = '{}.1'.format(PATH)
pyplugin.add_path(PATH)
- self._test_path_count(1)
+ test_path_count(self, 1)
pyplugin.add_path(second)
- self._test_path_count(2)
+ test_path_count(self, 2)
pyplugin.remove_path(PATH)
pyplugin.remove_path(second)
@@ -162,50 +174,52 @@
class TestModule(unittest.TestCase): # pylint:disable-msg=R0904
- """
- Tests the discovery method of pyplugin
- """
-
- def _test_path(self, path, count):
- """
- Given a module path, test that we find count plugins
- """
+ """ Tests the discovery method of pyplugin """
- self.assertEqual(len(pyplugin.get_paths()), 0)
- pyplugin.add_path(path)
-
- gen = pyplugin.find(Plugin)
-
- self.assertTrue(inspect.isgenerator(gen))
- self.assertEqual(len(list(gen)), count)
-
-
def setUp(self): # pylint:disable-msg=C0103
- paths = pyplugin.get_paths()
- for path in paths:
- pyplugin.remove_path(path)
+ pyplugin.clear_paths()
+
+
+ def tearDown(self): # pylint:disable-msg=C0103
+ pyplugin.clear_paths()
def test_single(self):
- """
- Test a module with a single plugin
- """
-
- self._test_path('modules.single', 1)
+ """ Test a module with a single plugin """
+ test_path(self, 'modules.single', 1)
def test_multiple(self):
- """
- Test a module with multiple plugins
- """
-
- self._test_path('modules.multiple', 2)
+ """ Test a module with multiple plugins """
+ test_path(self, 'modules.multiple', 2)
def test_mixed(self):
- """
- Test a module with more than just plugins
- """
+ """ Test a module with more than just plugins """
+ test_path(self, 'modules.mixed', 2)
+
+
+class TestPackage(unittest.TestCase): # pylint:disable-msg=R0904
+ """ Tests the find method of pyplugin on a package """
- self._test_path('modules.mixed', 2)
+ def setUp(self): # pylint:disable-msg=C0103
+ pyplugin.clear_paths()
+
+
+ def tearDown(self): # pylint:disable-msg=C0103
+ pyplugin.clear_paths()
+
+ def test_package_without_recurse(self):
+ """ Test a package without recursion """
+ test_path(self, 'modules', 0)
+
+
+ def test_package_recursion(self):
+ """ Test a package with recursion """
+ test_path(self, 'modules.deep', 2, recurse=True)
+
+ def test_package_deep_recursion(self):
+ """ Test a package with recursion """
+ test_path(self, 'modules', 7, recurse=True)
+