Plugin mirror: Parse plugins that import metadata

This commit is contained in:
Kovid Goyal 2013-08-22 06:11:11 +05:30
parent d844b5cb6a
commit 6a9fe9043a

View File

@ -105,9 +105,9 @@ def load_plugins_index():
return json.loads(bz2.decompress(raw))
# Get metadata from plugin zip file {{{
def convert_node(fields, x, names={}):
def convert_node(fields, x, names={}, import_data=None):
name = x.__class__.__name__
conv = lambda x:convert_node(fields, x, names=names)
conv = lambda x:convert_node(fields, x, names=names, import_data=import_data)
if name == 'Str':
return x.s.decode('utf-8') if isinstance(x.s, bytes) else x.s
elif name == 'Num':
@ -124,13 +124,34 @@ def convert_node(fields, x, names={}):
return tuple(map(conv, x.args))[0]
elif name == 'Name':
if x.id not in names:
if import_data is not None and x.id in import_data[0]:
return get_import_data(x.id, import_data[0][x.id], *import_data[1:])
raise ValueError('Could not find name %s for fields: %s' % (x.id, fields))
return names[x.id]
raise TypeError('Unknown datatype %s for fields: %s' % (x, fields))
Alias = namedtuple('Alias', 'name asname')
def parse_metadata(raw):
def get_import_data(name, mod, zf, names):
mod = mod.split('.')
if mod[0] == 'calibre_plugins':
mod = mod[2:]
mod = '/'.join(mod) + '.py'
if mod in names:
raw = zf.open(names[mod]).read()
module = ast.parse(raw, filename='__init__.py')
top_level_assigments = filter(lambda x:x.__class__.__name__ == 'Assign', ast.iter_child_nodes(module))
for node in top_level_assigments:
targets = {getattr(t, 'id', None) for t in node.targets}
targets.discard(None)
for x in targets:
if x == name:
return convert_node({x}, node.value)
raise ValueError('Failed to find name: %r in module: %r' % (name, mod))
else:
raise ValueError('Failed to find module: %r' % mod)
def parse_metadata(raw, namelist, zf):
module = ast.parse(raw, filename='__init__.py')
top_level_imports = filter(lambda x:x.__class__.__name__ == 'ImportFrom', ast.iter_child_nodes(module))
top_level_classes = tuple(filter(lambda x:x.__class__.__name__ == 'ClassDef', ast.iter_child_nodes(module)))
@ -138,6 +159,7 @@ def parse_metadata(raw):
defaults = {'name':'', 'description':'', 'supported_platforms':['windows', 'osx', 'linux'],
'version':(1, 0, 0), 'author':'Unknown', 'minimum_calibre_version':(0, 9, 42)}
field_names = set(defaults)
imported_names = {}
plugin_import_found = set()
all_imports = []
@ -156,16 +178,19 @@ def parse_metadata(raw):
plugin_import_found |= inames
else:
all_imports.append((mod, [n.name for n in names]))
imported_names[n.asname or n.name] = mod
if not plugin_import_found:
return all_imports
import_data = (imported_names, zf, namelist)
names = {}
for node in top_level_assigments:
targets = {getattr(t, 'id', None) for t in node.targets}
targets.discard(None)
for x in targets - field_names:
try:
val = convert_node({x}, node.value)
val = convert_node({x}, node.value, import_data=import_data)
except Exception:
pass
else:
@ -179,7 +204,7 @@ def parse_metadata(raw):
targets.discard(None)
fields = field_names.intersection(targets)
if fields:
val = convert_node(fields, node.value, names=names)
val = convert_node(fields, node.value, names=names, import_data=import_data)
for field in fields:
found[field] = val
return found
@ -218,7 +243,7 @@ def get_plugin_info(raw):
if metadata is None:
raise ValueError('No __init__.py found in plugin')
raw = zf.open(metadata).read()
ans = parse_metadata(raw)
ans = parse_metadata(raw, names, zf)
if isinstance(ans, dict):
return ans
# The plugin is importing its base class from somewhere else, le sigh
@ -229,7 +254,7 @@ def get_plugin_info(raw):
mod = '/'.join(mod) + '.py'
if mod in names:
raw = zf.open(names[mod]).read()
ans = parse_metadata(raw)
ans = parse_metadata(raw, names, zf)
if isinstance(ans, dict):
return ans
@ -512,10 +537,11 @@ class HelloWorld(FileTypePlugin):
'supported_platforms':['windows', 'osx', 'linux'],
'author':'Acme Inc.', 'version':{1:'a', 'b':2},
'minimum_calibre_version':(0, 7, 53)}
assert parse_metadata(raw) == vals
assert parse_metadata(raw, None, None) == vals
buf = io.BytesIO()
with zipfile.ZipFile(buf, 'w') as zf:
zf.writestr('very/lovely.py', raw)
zf.writestr('very/lovely.py', raw.replace(b'MV = (0, 7, 53)', b'from very.ver import MV'))
zf.writestr('very/ver.py', b'MV = (0, 7, 53)')
zf.writestr('__init__.py', b'from xxx import yyy\nfrom very.lovely import HelloWorld')
assert get_plugin_info(buf.getvalue()) == vals
@ -523,5 +549,7 @@ class HelloWorld(FileTypePlugin):
if __name__ == '__main__':
# test_parse_metadata()
main()
# import pprint
# pprint.pprint(get_plugin_info(open(sys.argv[-1], 'rb').read()))
main()