From 8baacc366b7897462e926c274d4f69576fd57a3f Mon Sep 17 00:00:00 2001 From: gandalf3 Date: Sun, 2 Jul 2017 15:15:48 -0700 Subject: [PATCH] Split repo generation functionality out of blenderpack.py Moved repo.json generation to make_repo.py Package/addon parsing is getting a bit messy, this can be cleaned up when we have a clearer idea of what a package is. For now just make it work. --- blenderpack.py | 88 ---------- make_repo.py | 162 ++++++++++++++++++ .../{zipped_addon.zip => dir_addon.zip} | Bin 4291 -> 4291 bytes .../test_helpers/addons/dir_invalid_addon.zip | Bin 0 -> 640 bytes .../addons/dir_invalid_addon/__init__.py | 12 ++ tests/test_helpers/addons/dir_nonaddon.zip | Bin 370 -> 370 bytes tests/test_helpers/addons/invalid_addon.zip | Bin 0 -> 450 bytes .../addons/{not_an_addon.py => nonaddon.py} | 0 tests/test_helpers/addons/nonaddon.zip | Bin 0 -> 172 bytes .../{real_addon.py => singlefile_addon.py} | 31 +++- .../test_helpers/addons/singlefile_addon.zip | Bin 0 -> 7303 bytes .../addons/zipped_single_file_addon.zip | Bin 7185 -> 0 bytes tests/test_helpers/dir_addon_output | 1 - tests/test_helpers/expected_blinfo | 13 ++ tests/test_helpers/real_addon.py_output | 1 - tests/test_helpers/zipped_addon.zip_output | 1 - tests/test_make_repo.py | 72 +++++--- utils.py | 6 + 18 files changed, 260 insertions(+), 127 deletions(-) create mode 100755 make_repo.py rename tests/test_helpers/addons/{zipped_addon.zip => dir_addon.zip} (93%) create mode 100644 tests/test_helpers/addons/dir_invalid_addon.zip create mode 100644 tests/test_helpers/addons/dir_invalid_addon/__init__.py create mode 100644 tests/test_helpers/addons/invalid_addon.zip rename tests/test_helpers/addons/{not_an_addon.py => nonaddon.py} (100%) create mode 100644 tests/test_helpers/addons/nonaddon.zip rename tests/test_helpers/addons/{real_addon.py => singlefile_addon.py} (97%) create mode 100644 tests/test_helpers/addons/singlefile_addon.zip delete mode 100644 tests/test_helpers/addons/zipped_single_file_addon.zip delete mode 100644 tests/test_helpers/dir_addon_output create mode 100644 tests/test_helpers/expected_blinfo delete mode 100644 tests/test_helpers/real_addon.py_output delete mode 100644 tests/test_helpers/zipped_addon.zip_output create mode 100644 utils.py diff --git a/blenderpack.py b/blenderpack.py index d0f382b..6d8096e 100755 --- a/blenderpack.py +++ b/blenderpack.py @@ -60,94 +60,6 @@ def fetch(url, pipe): # def json(self): # return json.dumps(self.__dict__) -def parse_blinfo(source: str) -> dict: - """Parse a python file and return its bl_info dict, if there is one (else return None)""" - - tree = ast.parse(source) - - for body in tree.body: - if body.__class__ != ast.Assign: - continue - if len(body.targets) != 1: - continue - if getattr(body.targets[0], 'id', '') != 'bl_info': - continue - - return ast.literal_eval(body.value) - - raise BadAddon('No bl_info found') - - -def extract_blinfo(path: pathlib.Path) -> dict: - """Extract bl_info dict from addon at path (can be single file, module, or zip)""" - - source = None - # get last component of path - addon_name = path.name - - if path.is_dir(): - with open(path / '__init__.py', 'r') as f: - source = f.read() - else: - - # HACK: perhaps not the best approach determining filetype..? - try: - with zipfile.ZipFile(str(path), 'r') as z: - for fname in z.namelist(): - # HACK: this seems potentially fragile; depends on zipfile listing root contents first - if fname.endswith('__init__.py'): - source = z.read(fname) - break - except zipfile.BadZipFile: - with path.open() as f: - source = f.read() - - if source == None: - raise RuntimeError("Could not read addon '%s'" % addon_name) - - return parse_blinfo(source) - - - -def make_repo(repopath: pathlib.Path): - """Make repo.json for files in directory 'repopath'""" - - repo_data = {} - package_data = [] - - if not repopath.is_dir(): - raise FileNotFoundError(repopath) - - for addon_path in repopath.iterdir(): - package_datum = {} - addon = addon_path.name - - try: - bl_info = extract_blinfo(addon_path) - except BadAddon as err: - log.warning('Could not extract bl_info from {}: {}'.format(addon_path, err)) - continue - - if not REQUIRED_KEYS.issubset(set(bl_info)): - log.warning( - "Required key(s) '{}' not found in bl_info of '{}'".format( - "', '".join(REQUIRED_KEYS.difference(set(bl_info))), addon) - ) - - package_datum['bl_info'] = bl_info - package_datum['type'] = 'addon' - package_data.append(package_datum) - - repo_data['packages'] = package_data - - log.info("Repository generation successful") - cwd = pathlib.Path.cwd() - dump_repo(cwd, repo_data) - log.info("repo.json written to %s" % cwd) - -def dump_repo(repo_path: pathlib.Path, repo_data: dict): - with (repo_path / 'repo.json').open('w', encoding='utf-8') as repo_file: - json.dump(repo_data, repo_file, indent=4, sort_keys=True) def main(): diff --git a/make_repo.py b/make_repo.py new file mode 100755 index 0000000..f13128a --- /dev/null +++ b/make_repo.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python3 + +from pathlib import Path +import argparse +import zipfile +import ast +import json +import logging +from utils import * + +log = logging.getLogger(__name__) + +REQUIRED_KEYS = set(['name', 'blender', 'version']) +SCHEMA_VERSION = 1 + +class BadAddon(Exception): + pass + +def iter_addons(path: Path) -> (Path, dict): + """ + Generator, yields (path, bl_info) of blender addons in `path` (non recursive). + """ + pass + # for item in addons_dir.iterdir(): + # base = item.name + # + # yield (base, fname, '.zip') + # else: + # yield (base, item.path, '.py') + +def parse_blinfo(source: str) -> dict: + """Parse a python file and return its bl_info dict, if there is one (else return None)""" + + try: + tree = ast.parse(source) + except SyntaxError as e: + raise BadAddon("Syntax error") from e + + for body in tree.body: + if body.__class__ != ast.Assign: + continue + if len(body.targets) != 1: + continue + if getattr(body.targets[0], 'id', '') != 'bl_info': + continue + + return ast.literal_eval(body.value) + + raise BadAddon('No bl_info found') + + +def extract_blinfo(item: Path) -> dict: + """ + Extract bl_info dict from addon at path (can be single file, python package, or zip) + """ + + blinfo = None + addon_name = item.name + + if not item.exists(): + raise FileNotFoundError("Cannot extract blinfo from '%s'; no such file or directory" % item) + + if item.is_dir(): + fname = item / '__init__.py' + try: + with fname.open("r") as f: + blinfo = parse_blinfo(f.read()) + except FileNotFoundError as err: + # directory with no __init__.py: not an addon + raise BadAddon("Directory '%s' doesn't contain __init__.py; not a python package" % item) from err + + elif item.is_file(): + try: + with zipfile.ZipFile(str(item), 'r') as z: + if len(z.namelist()) == 1: + # zipfile with one item: just read that item + blinfo = parse_blinfo(z.read(z.namelist()[0])) + else: + # zipfile with multiple items: try all __init__.py files + for fname in z.namelist(): + # TODO: zips with multiple bl_infos might be a problem, + # not sure how such cases should be handled (if at all) + if fname.endswith('__init__.py'): + try: + blinfo = parse_blinfo(z.read(fname)) + break + except BadAddon: + continue + raise BadAddon("Zipfile '%s' doesn't contain any readable bl_info dict" % item) + + except zipfile.BadZipFile: + # Assume file is + with item.open() as f: + blinfo = parse_blinfo(f.read()) + + # This should not happen + if blinfo == None: + raise RuntimeError("Could not read addon '%s'" % addon_name) + + return blinfo + + + +def make_repo(repopath: Path): + """Make repo.json for files in directory 'repopath'""" + + repo_data = {} + package_data = [] + + if not repopath.is_dir(): + raise FileNotFoundError(repopath) + + for addon_path in repopath.iterdir(): + package_datum = {} + addon = addon_path.name + + try: + bl_info = extract_blinfo(addon_path) + except BadAddon as err: + log.warning('Could not extract bl_info from {}: {}'.format(addon_path, err)) + continue + + if not REQUIRED_KEYS.issubset(set(bl_info)): + log.warning( + "Required key(s) '{}' not found in bl_info of '{}'".format( + "', '".join(REQUIRED_KEYS.difference(set(bl_info))), addon) + ) + + package_datum['bl_info'] = bl_info + package_datum['type'] = 'addon' + package_data.append(package_datum) + + repo_data['packages'] = package_data + + log.info("Repository generation successful") + cwd = Path.cwd() + dump_repo(cwd, repo_data) + log.info("repo.json written to %s" % cwd) + + +def main(): + parser = argparse.ArgumentParser(description='Generate a blender package repository from a directory of addons') + + parser.add_argument('-v', '--verbose', + help="Increase verbosity (can be used multiple times)", + action="count", + default=0) + parser.add_argument('path', + type=Path, + nargs='?', + default=Path.cwd(), + help="Path to addon directory") + + args = parser.parse_args() + log.level = args.verbose + logging.basicConfig(level=logging.INFO, + format='%(levelname)8s: %(message)s') + + make_repo(args.path) + +if __name__ == '__main__': + main() diff --git a/tests/test_helpers/addons/zipped_addon.zip b/tests/test_helpers/addons/dir_addon.zip similarity index 93% rename from tests/test_helpers/addons/zipped_addon.zip rename to tests/test_helpers/addons/dir_addon.zip index b7682c6d9a434aa6f0f6621d2f8675f26564012c..f130ac3390a2de3d07883963b51eb64928421e83 100644 GIT binary patch delta 54 zcmX@Ccvx|QA#!j delta 54 zcmX@Ccvx|QAv5Pr--#wZ%+5P}H#Rr$GAH)8}W I>wIq*0SEgNA^-pY diff --git a/tests/test_helpers/addons/dir_invalid_addon.zip b/tests/test_helpers/addons/dir_invalid_addon.zip new file mode 100644 index 0000000000000000000000000000000000000000..23c87b06ba4a99cc50bea66b66f2e5a3e6f69617 GIT binary patch literal 640 zcmWIWW@h1H0D+su552$)C?Uik!;q3$6rY(_mY9>75}%lolAos^8p6rI+$9nbIS+_S zE4UdLSza(RFtDTmbp-&;5&_a2V6#eQPju&DWMEhW#Ijh;iU*pVSrQ+wS5OHz7GyM< zv8e&x-iI9o_I(fMsa~}qCEMj-Q$KasTewYPMG_bxxZb z-%mCxO?$JvS^CFJM~#Ki>ts4rIo!_Msk>z7yi{EiKR;aP-padK)$xmUnRK2cu6F)( z?B)iucn^!|6Xx~K^__FX!@^%RCeAy;XYH-0v-qPQ9e(ickzn4#bsC3$UkGX!u53Km zmbvVr!NY|v-Ag374PyDU7Rv(TE#xxe;ONN?3G<69M6`+X*BZ+&;Qp8Zj7$K$IL zg7%iwZgaBcY@40%a)tb2IscE=znKHP8JXmmaYdK}H0lHx-a3Mq*rJaW5`Ac43^5m1 j Add > Curve", + "description": "Adds generated ivy to a mesh object starting " + "at the 3D cursor", + "warning": "", + "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/" + "Scripts/Curve/Ivy_Gen", + "category": "Add Curve", +} diff --git a/tests/test_helpers/addons/dir_nonaddon.zip b/tests/test_helpers/addons/dir_nonaddon.zip index 2237d7dbe3f97d5566113fe6673bb7bd30c23c85..068af94a44ca195c99ac7165b4aaf627e2ab2398 100644 GIT binary patch delta 12 Tcmeyw^oeOgKU0>_#EB09BZUQt delta 12 Ucmeyw^oeOgKhqPzi4z|J043uF0ssI2 diff --git a/tests/test_helpers/addons/invalid_addon.zip b/tests/test_helpers/addons/invalid_addon.zip new file mode 100644 index 0000000000000000000000000000000000000000..d5ef6203d9d31693a02afff015d6d978f4392828 GIT binary patch literal 450 zcmWIWW@Zs#U|`^2=vi{jt7P^>cOFIthBZJez#zkrnOByWlbI5qn39s8r&mxJ8p6rI zyuHUKa<@o$WN8IA10%}|W(Ee96rc&I0p8w+9R&7$59g^~wILR zl9R8Nn4j^x#P)Ij?%8U#S1ff-n;YLxHY-hgv%FdQ$4p0!h0*I|I#oH`&fBTGWaqq8 zT@yb)Tp#KH_^R!AtLg>Zm3D;r2XBM^oG=`fHH3; +# bl_info = { +# "name": "IvyGen", +# "author": "testscreenings, PKHG, TrumanBlending", +# "version": (0, 1, 2), +# "blender": (2, 59, 0), +# "location": "View3D > Add > Curve", +# "description": "Adds generated ivy to a mesh object starting " +# "at the 3D cursor", +# "warning": "", +# "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/" +# "Scripts/Curve/Ivy_Gen", +# "category": "Add Curve", +# } + +# just use one blinfo for all addons to simplify testing bl_info = { - "name": "IvyGen", - "author": "testscreenings, PKHG, TrumanBlending", + "name": "Extra Objects", + "author": "Multiple Authors", "version": (0, 1, 2), - "blender": (2, 59, 0), - "location": "View3D > Add > Curve", - "description": "Adds generated ivy to a mesh object starting " - "at the 3D cursor", + "blender": (2, 76, 0), + "location": "View3D > Add > Curve > Extra Objects", + "description": "Add extra curve object types", "warning": "", "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/" - "Scripts/Curve/Ivy_Gen", - "category": "Add Curve", + "Scripts/Curve/Curve_Objects", + "category": "Add Curve" } + import bpy from bpy.props import ( FloatProperty, diff --git a/tests/test_helpers/addons/singlefile_addon.zip b/tests/test_helpers/addons/singlefile_addon.zip new file mode 100644 index 0000000000000000000000000000000000000000..e92278ad18a5782b3e9f06f63326b1604f1d47ad GIT binary patch literal 7303 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�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 RjPPG*{8!8WgFgV^e*k8Pw|f8p diff --git a/tests/test_helpers/dir_addon_output b/tests/test_helpers/dir_addon_output deleted file mode 100644 index f7a28bd..0000000 --- a/tests/test_helpers/dir_addon_output +++ /dev/null @@ -1 +0,0 @@ -{'name': 'Extra Objects', 'author': 'Multiple Authors', 'version': (0, 1, 2), 'blender': (2, 76, 0), 'location': 'View3D > Add > Curve > Extra Objects', 'description': 'Add extra curve object types', 'warning': '', 'wiki_url': 'https://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Curve/Curve_Objects', 'category': 'Add Curve'} \ No newline at end of file diff --git a/tests/test_helpers/expected_blinfo b/tests/test_helpers/expected_blinfo new file mode 100644 index 0000000..0628ff6 --- /dev/null +++ b/tests/test_helpers/expected_blinfo @@ -0,0 +1,13 @@ +{ + "name": "Extra Objects", + "author": "Multiple Authors", + "version": (0, 1, 2), + "blender": (2, 76, 0), + "location": "View3D > Add > Curve > Extra Objects", + "description": "Add extra curve object types", + "warning": "", + "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/" + "Scripts/Curve/Curve_Objects", + "category": "Add Curve" + } + diff --git a/tests/test_helpers/real_addon.py_output b/tests/test_helpers/real_addon.py_output deleted file mode 100644 index 7543bc5..0000000 --- a/tests/test_helpers/real_addon.py_output +++ /dev/null @@ -1 +0,0 @@ -{'name': 'IvyGen', 'author': 'testscreenings, PKHG, TrumanBlending', 'version': (0, 1, 2), 'blender': (2, 59, 0), 'location': 'View3D > Add > Curve', 'description': 'Adds generated ivy to a mesh object starting at the 3D cursor', 'warning': '', 'wiki_url': 'https://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Curve/Ivy_Gen', 'category': 'Add Curve'} \ No newline at end of file diff --git a/tests/test_helpers/zipped_addon.zip_output b/tests/test_helpers/zipped_addon.zip_output deleted file mode 100644 index f7a28bd..0000000 --- a/tests/test_helpers/zipped_addon.zip_output +++ /dev/null @@ -1 +0,0 @@ -{'name': 'Extra Objects', 'author': 'Multiple Authors', 'version': (0, 1, 2), 'blender': (2, 76, 0), 'location': 'View3D > Add > Curve > Extra Objects', 'description': 'Add extra curve object types', 'warning': '', 'wiki_url': 'https://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Curve/Curve_Objects', 'category': 'Add Curve'} \ No newline at end of file diff --git a/tests/test_make_repo.py b/tests/test_make_repo.py index 792520f..254d69b 100644 --- a/tests/test_make_repo.py +++ b/tests/test_make_repo.py @@ -2,11 +2,15 @@ import unittest from pathlib import Path -import os +import logging +import ast import json -import blenderpack +import make_repo -class test_blenderpack_make_repo(unittest.TestCase): +logging.basicConfig(level=logging.DEBUG, + format='%(levelname)8s: %(message)s') + +class test_make_repo(unittest.TestCase): helper_path = Path('tests', 'test_helpers') addon_path = helper_path / 'addons' @@ -15,20 +19,12 @@ class test_blenderpack_make_repo(unittest.TestCase): test_file = 'file_that_doesnt_exist' self.assertRaises( FileNotFoundError, - blenderpack.extract_blinfo, - self.addon_path / test_file - ) - - def test_extract_blinfo_from_nonaddon(self): - test_file = 'not_an_addon.py' - self.assertRaises( - blenderpack.BadAddon, - blenderpack.extract_blinfo, + make_repo.extract_blinfo, self.addon_path / test_file ) def test_make_repo_valid(self): - blenderpack.make_repo(self.helper_path / 'addons') + make_repo.make_repo(self.helper_path / 'addons') repojson = Path.cwd() / 'repo.json' try: @@ -40,26 +36,46 @@ class test_blenderpack_make_repo(unittest.TestCase): self.fail('unfinished test') def test_make_repo_from_nonexistent(self): - blenderpack.make_repo(self.helper_path / 'addons') + make_repo.make_repo(self.helper_path / 'addons') self.fail('unfinished test') +# addons which should contain bl_infos +yes_blinfo = [ + f for f in test_make_repo.addon_path.iterdir() + if not f.match('*nonaddon*') and not f.match('*invalid_addon*') + ] +# addons which should throw BadAddon because they have no blinfo +no_blinfo = [ + f for f in test_make_repo.addon_path.iterdir() + if f.match('*nonaddon*') + ] -# testname: filename -bl_info_tests = { - 'test_extract_blinfo_from_file': 'real_addon.py', - 'test_extract_blinfo_from_zip': 'zipped_addon.zip', - 'test_extract_blinfo_from_dir': 'dir_addon', -} - -def generate_test(test_file): +def generate_good_blinfo_test(test_file: Path): def test(self): - reality = str(blenderpack.extract_blinfo(self.addon_path / test_file)) - with (self.helper_path / (test_file + '_output')).open() as f: - expectation = f.read() + reality = make_repo.extract_blinfo(test_file) + with (self.helper_path / 'expected_blinfo').open("r") as f: + expectation = ast.literal_eval(f.read()) self.assertEqual(expectation, reality) return test -for name, param in bl_info_tests.items(): - test_func = generate_test(param) - setattr(test_blenderpack_make_repo, 'test_{}'.format(name), test_func) +def generate_bad_blinfo_test(test_file: Path): + def test(self): + self.assertRaises( + make_repo.BadAddon, + make_repo.extract_blinfo, + test_file + ) + return test + +# Add test method retur +def add_generated_tests(test_generator, params, destclass): + """ + Add a test method (as returned by 'test_generator') to destclass for every param + """ + for param in params: + test_func = test_generator(param) + setattr(destclass, 'test_{}'.format(param), test_func) + +add_generated_tests(generate_good_blinfo_test, yes_blinfo, test_make_repo) +add_generated_tests(generate_bad_blinfo_test, no_blinfo, test_make_repo) diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..fd2f6b2 --- /dev/null +++ b/utils.py @@ -0,0 +1,6 @@ +from pathlib import Path +import json + +def dump_repo(repo_path: Path, repo_data: dict): + with (repo_path / 'repo.json').open('w', encoding='utf-8') as repo_file: + json.dump(repo_data, repo_file, indent=4, sort_keys=True)