mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Plugin mirror: Parse plugins that import metadata
This commit is contained in:
parent
d844b5cb6a
commit
6a9fe9043a
@ -105,9 +105,9 @@ def load_plugins_index():
|
|||||||
return json.loads(bz2.decompress(raw))
|
return json.loads(bz2.decompress(raw))
|
||||||
|
|
||||||
# Get metadata from plugin zip file {{{
|
# 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__
|
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':
|
if name == 'Str':
|
||||||
return x.s.decode('utf-8') if isinstance(x.s, bytes) else x.s
|
return x.s.decode('utf-8') if isinstance(x.s, bytes) else x.s
|
||||||
elif name == 'Num':
|
elif name == 'Num':
|
||||||
@ -124,13 +124,34 @@ def convert_node(fields, x, names={}):
|
|||||||
return tuple(map(conv, x.args))[0]
|
return tuple(map(conv, x.args))[0]
|
||||||
elif name == 'Name':
|
elif name == 'Name':
|
||||||
if x.id not in names:
|
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))
|
raise ValueError('Could not find name %s for fields: %s' % (x.id, fields))
|
||||||
return names[x.id]
|
return names[x.id]
|
||||||
raise TypeError('Unknown datatype %s for fields: %s' % (x, fields))
|
raise TypeError('Unknown datatype %s for fields: %s' % (x, fields))
|
||||||
|
|
||||||
Alias = namedtuple('Alias', 'name asname')
|
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')
|
module = ast.parse(raw, filename='__init__.py')
|
||||||
top_level_imports = filter(lambda x:x.__class__.__name__ == 'ImportFrom', ast.iter_child_nodes(module))
|
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)))
|
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'],
|
defaults = {'name':'', 'description':'', 'supported_platforms':['windows', 'osx', 'linux'],
|
||||||
'version':(1, 0, 0), 'author':'Unknown', 'minimum_calibre_version':(0, 9, 42)}
|
'version':(1, 0, 0), 'author':'Unknown', 'minimum_calibre_version':(0, 9, 42)}
|
||||||
field_names = set(defaults)
|
field_names = set(defaults)
|
||||||
|
imported_names = {}
|
||||||
|
|
||||||
plugin_import_found = set()
|
plugin_import_found = set()
|
||||||
all_imports = []
|
all_imports = []
|
||||||
@ -156,16 +178,19 @@ def parse_metadata(raw):
|
|||||||
plugin_import_found |= inames
|
plugin_import_found |= inames
|
||||||
else:
|
else:
|
||||||
all_imports.append((mod, [n.name for n in names]))
|
all_imports.append((mod, [n.name for n in names]))
|
||||||
|
imported_names[n.asname or n.name] = mod
|
||||||
if not plugin_import_found:
|
if not plugin_import_found:
|
||||||
return all_imports
|
return all_imports
|
||||||
|
|
||||||
|
import_data = (imported_names, zf, namelist)
|
||||||
|
|
||||||
names = {}
|
names = {}
|
||||||
for node in top_level_assigments:
|
for node in top_level_assigments:
|
||||||
targets = {getattr(t, 'id', None) for t in node.targets}
|
targets = {getattr(t, 'id', None) for t in node.targets}
|
||||||
targets.discard(None)
|
targets.discard(None)
|
||||||
for x in targets - field_names:
|
for x in targets - field_names:
|
||||||
try:
|
try:
|
||||||
val = convert_node({x}, node.value)
|
val = convert_node({x}, node.value, import_data=import_data)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
@ -179,7 +204,7 @@ def parse_metadata(raw):
|
|||||||
targets.discard(None)
|
targets.discard(None)
|
||||||
fields = field_names.intersection(targets)
|
fields = field_names.intersection(targets)
|
||||||
if fields:
|
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:
|
for field in fields:
|
||||||
found[field] = val
|
found[field] = val
|
||||||
return found
|
return found
|
||||||
@ -218,7 +243,7 @@ def get_plugin_info(raw):
|
|||||||
if metadata is None:
|
if metadata is None:
|
||||||
raise ValueError('No __init__.py found in plugin')
|
raise ValueError('No __init__.py found in plugin')
|
||||||
raw = zf.open(metadata).read()
|
raw = zf.open(metadata).read()
|
||||||
ans = parse_metadata(raw)
|
ans = parse_metadata(raw, names, zf)
|
||||||
if isinstance(ans, dict):
|
if isinstance(ans, dict):
|
||||||
return ans
|
return ans
|
||||||
# The plugin is importing its base class from somewhere else, le sigh
|
# 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'
|
mod = '/'.join(mod) + '.py'
|
||||||
if mod in names:
|
if mod in names:
|
||||||
raw = zf.open(names[mod]).read()
|
raw = zf.open(names[mod]).read()
|
||||||
ans = parse_metadata(raw)
|
ans = parse_metadata(raw, names, zf)
|
||||||
if isinstance(ans, dict):
|
if isinstance(ans, dict):
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
@ -512,10 +537,11 @@ class HelloWorld(FileTypePlugin):
|
|||||||
'supported_platforms':['windows', 'osx', 'linux'],
|
'supported_platforms':['windows', 'osx', 'linux'],
|
||||||
'author':'Acme Inc.', 'version':{1:'a', 'b':2},
|
'author':'Acme Inc.', 'version':{1:'a', 'b':2},
|
||||||
'minimum_calibre_version':(0, 7, 53)}
|
'minimum_calibre_version':(0, 7, 53)}
|
||||||
assert parse_metadata(raw) == vals
|
assert parse_metadata(raw, None, None) == vals
|
||||||
buf = io.BytesIO()
|
buf = io.BytesIO()
|
||||||
with zipfile.ZipFile(buf, 'w') as zf:
|
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')
|
zf.writestr('__init__.py', b'from xxx import yyy\nfrom very.lovely import HelloWorld')
|
||||||
assert get_plugin_info(buf.getvalue()) == vals
|
assert get_plugin_info(buf.getvalue()) == vals
|
||||||
|
|
||||||
@ -523,5 +549,7 @@ class HelloWorld(FileTypePlugin):
|
|||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# test_parse_metadata()
|
# test_parse_metadata()
|
||||||
main()
|
# import pprint
|
||||||
|
# pprint.pprint(get_plugin_info(open(sys.argv[-1], 'rb').read()))
|
||||||
|
|
||||||
|
main()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user