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.
This commit is contained in:
David Wilson 2018-05-08 13:22:19 +01:00
parent 48535cc661
commit f96c552f87
1 changed files with 137 additions and 0 deletions

View File

@ -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()))