From f96c552f87c85cf433d9aed6ba84c911c58760c3 Mon Sep 17 00:00:00 2001 From: David Wilson Date: Tue, 8 May 2018 13:22:19 +0100 Subject: [PATCH] issue #217: initial module scanner code. This is sketch code, it's being done separately from mitogen.master.* to begin with to avoid breaking what's there. --- ansible_mitogen/module_finder.py | 137 +++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 ansible_mitogen/module_finder.py diff --git a/ansible_mitogen/module_finder.py b/ansible_mitogen/module_finder.py new file mode 100644 index 00000000..1b2e1850 --- /dev/null +++ b/ansible_mitogen/module_finder.py @@ -0,0 +1,137 @@ + +import imp +import os +import sys +import mitogen.master + + +class Name(object): + def __str__(self): + return self.identifier + + def __repr__(self): + return 'Name(%r)' % (self.identifier,) + + def __init__(self, identifier): + self.identifier = identifier + + def head(self): + head, _, tail = self.identifier.partition('.') + return head + + def tail(self): + head, _, tail = self.identifier.partition('.') + return tail + + def pop_n(self, level): + name = self.identifier + for _ in xrange(level): + if '.' not in name: + return None + name, _, _ = self.identifier.rpartition('.') + return Name(name) + + def append(self, part): + return Name('%s.%s' % (self.identifier, part)) + + +class Module(object): + def __init__(self, name, path, kind=imp.PY_SOURCE, parent=None): + self.name = Name(name) + self.path = path + if kind == imp.PKG_DIRECTORY: + self.path = os.path.join(self.path, '__init__.py') + self.kind = kind + self.parent = parent + + def fullname(self): + bits = [str(self.name)] + while self.parent: + bits.append(str(self.parent.name)) + self = self.parent + return '.'.join(reversed(bits)) + + def __repr__(self): + return 'Module(%r, path=%r, parent=%r)' % ( + self.name, + self.path, + self.parent, + ) + + def dirname(self): + return os.path.dirname(self.path) + + def code(self): + fp = open(self.path) + try: + return compile(fp.read(), str(self.name), 'exec') + finally: + fp.close() + + +def find(name, path=(), parent=None): + """ + (Name, search path) -> Module instance or None. + """ + try: + tup = imp.find_module(name.head(), list(path)) + except ImportError: + return parent + + fp, path, (suffix, mode, kind) = tup + if fp: + fp.close() + + module = Module(name.head(), path, kind, parent) + if name.tail(): + return find_relative(module, Name(name.tail()), path) + return module + + +def find_relative(parent, name, path=()): + path = [parent.dirname()] + list(path) + return find(name, path, parent=parent) + + +def path_pop(s, n): + return os.pathsep.join(s.split(os.pathsep)[-n:]) + + +def scan(module, path): + scanner = mitogen.master.scan_code_imports(module.code()) + for level, modname_s, fromlist in scanner: + modname = Name(modname_s) + if level == -1: + imported = find_relative(module, modname, path) + elif level: + subpath = [path_pop(module.dirname(), level)] + list(path) + imported = find(modname.pop_n(level), subpath) + else: + imported = find(modname.pop_n(level), path) + + if imported and mitogen.master.is_stdlib_path(imported.path): + continue + + if imported and fromlist: + have = False + for fromname_s in fromlist: + fromname = modname.append(fromname_s) + f_imported = find_relative(imported, fromname, path) + if f_imported and f_imported.fullname() == fromname.identifier: + have = True + yield fromname, f_imported, None + if have: + continue + + if imported: + yield modname, imported + + +module = Module(name='ansible_module_apt', path='/Users/dmw/src/mitogen/.venv/lib/python2.7/site-packages/ansible/modules/packaging/os/apt.py') +path = tuple(sys.path) +path = ('/Users/dmw/src/ansible/lib',) + path + + +from pprint import pprint +for name, imported in scan(module, sys.path): + print '%s: %s' % (name, imported and (str(name) == imported.fullname()))