Cleanup: Changes suggested by @sybren

This commit is contained in:
gandalf3
2017-07-05 18:40:32 -07:00
parent 1cefb4eab6
commit f20264c963
2 changed files with 78 additions and 73 deletions

View File

@@ -20,10 +20,9 @@ def iter_addons(path: Path) -> (Path, dict):
""" """
Generator, yields (path, bl_info) of blender addons in `path`. Generator, yields (path, bl_info) of blender addons in `path`.
""" """
pass
for item in path.iterdir(): for item in path.iterdir():
try: try:
yield(item, extract_blinfo(item)) yield (item, extract_blinfo(item))
except BadAddon as err: except BadAddon as err:
log.debug("Skipping '{}': {}".format(item.name, err)) log.debug("Skipping '{}': {}".format(item.name, err))
@@ -48,104 +47,122 @@ def parse_blinfo(source: str) -> dict:
raise BadAddon('No bl_info found') raise BadAddon('No bl_info found')
def blinfo_from_zip(item: Path) -> dict:
try:
with zipfile.ZipFile(str(item), 'r') as z:
if len(z.namelist()) == 1:
# zipfile with one item: just read that item
return 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)
# for now we just break after the first one
if fname.endswith('__init__.py'):
try:
return parse_blinfo(z.read(fname))
except BadAddon:
continue
raise BadAddon("Zipfile '%s' doesn't contain a readable bl_info dict" % item)
except zipfile.BadZipFile as err:
raise BadAddon("Bad zipfile '%s'" % item) from err
def blinfo_from_py(item: Path) -> dict:
with item.open() as f:
return parse_blinfo(f.read())
def blinfo_from_dir(item: Path) -> dict:
try:
f = (item / '__init__.py').open("r")
except FileNotFoundError as err:
raise BadAddon("Directory '%s' doesn't contain __init__.py; not a python package" % item) from err
with f:
return parse_blinfo(f.read())
def extract_blinfo(item: Path) -> dict: def extract_blinfo(item: Path) -> dict:
""" """
Extract bl_info dict from addon at path (can be single file, python package, or zip) Extract bl_info dict from addon at path (can be single file, python package, or zip)
""" """
blinfo = None
if not item.exists(): if not item.exists():
raise FileNotFoundError("Cannot extract blinfo from '%s'; no such file or directory" % item) raise FileNotFoundError("Cannot extract blinfo from '%s'; no such file or directory" % item)
if item.is_dir(): if item.is_dir():
return blinfo_from_dir(item)
try:
f = (item / '__init__.py').open("r")
except FileNotFoundError as err:
raise BadAddon("Directory '%s' doesn't contain __init__.py; not a python package" % item) from err
with f:
blinfo = parse_blinfo(f.read())
elif item.is_file(): if item.is_file():
ext = item.suffix.lower() ext = item.suffix.lower()
if ext == '.zip': if ext == '.zip':
try: return blinfo_from_zip(item)
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)
# for now we just break after the first one
if fname.endswith('__init__.py'):
try:
blinfo = parse_blinfo(z.read(fname))
break
except BadAddon:
continue
if blinfo is None:
raise BadAddon("Zipfile '%s' doesn't contain a readable bl_info dict" % item)
except zipfile.BadZipFile as e:
raise BadAddon("Bad zipfile '%s'" % item) from e
elif ext == '.py': elif ext == '.py':
with item.open() as f: return blinfo_from_py(item)
blinfo = parse_blinfo(f.read())
else: else:
raise BadAddon("File '%s' doesn't have a .zip or .py extension; not an addon" % item) raise BadAddon("File '%s' doesn't have a .zip or .py extension; not an addon" % item)
# This should not happen # This should not happen
if blinfo is None: raise RuntimeError("Could not read addon '%s'" % item.name)
raise RuntimeError("Could not read addon '%s'" % item.name)
return blinfo
class Package: class Package:
def __init__(self, path: Path, bl_info: dict, baseurl=None): """
Stores package path and metadata
"""
def __init__(self, path: Path, bl_info: dict):
self.bl_info = bl_info self.bl_info = bl_info
self.path = path self.path = path
self.url = None self.url = None
def dict(self) -> dict: def to_dict(self) -> dict:
"""
Return a dict representation of the package
"""
return { return {
'bl_info': self.bl_info, 'bl_info': self.bl_info,
'url': self.url, 'url': self.url,
} }
class Repository: class Repository:
"""
Stores repository metadata (including a list of packages)
"""
def __init__(self, name: str): def __init__(self, name: str):
self.name = name self.name = name
self.url = None self.url = None
self.packages = [] self.packages = []
def add_package(self, pkg: Package): def add_package(self, pkg: Package):
"""
Add a package to the repository
"""
# if pkg.url is None: # if pkg.url is None:
# pkg.url = # pkg.url =
self.packages.append(pkg) self.packages.append(pkg)
def dict(self) -> dict: def to_dict(self) -> dict:
"""
Return a dict representation of the repository
"""
return { return {
'name': self.name, 'name': self.name,
'packages': [p.dict() for p in self.packages], 'packages': [p.to_dict() for p in self.packages],
'url': self.url, 'url': self.url,
} }
def dump(self, path: Path): def dump(self, path: Path):
"""
Dump repository as a repo.json file in 'path'
"""
with (path / 'repo.json').open('w', encoding='utf-8') as repo_file: with (path / 'repo.json').open('w', encoding='utf-8') as repo_file:
json.dump(self.dict(), repo_file, indent=4, sort_keys=True) json.dump(self.to_dict(), repo_file, indent=4, sort_keys=True)
log.info("repo.json written to %s" % path) log.info("repo.json written to %s" % path)
def json(self) -> str:
return json.dumps(self.__dict__)
def make_repo(path: Path, name: str) -> Repository: def make_repo(path: Path, name: str) -> Repository:
"""Make repo.json for files in directory 'path'""" """Make repo.json for files in directory 'path'"""
@@ -188,12 +205,15 @@ def main():
help="Path to addon directory") help="Path to addon directory")
args = parser.parse_args() args = parser.parse_args()
if args.name is None:
args.name = args.path.name
logging.basicConfig(format='%(levelname)8s: %(message)s', level=logging.INFO) logging.basicConfig(format='%(levelname)8s: %(message)s', level=logging.INFO)
log.level += args.verbose log.level += args.verbose
if args.name is None:
args.name = args.path.name
log.debug("Repository name: '%s'" % args.name)
repo = make_repo(args.path, args.name) repo = make_repo(args.path, args.name)
repo.dump(args.output) repo.dump(args.output)

View File

@@ -10,7 +10,7 @@ import make_repo
logging.basicConfig(level=logging.DEBUG, logging.basicConfig(level=logging.DEBUG,
format='%(levelname)8s: %(message)s') format='%(levelname)8s: %(message)s')
class test_make_repo(unittest.TestCase): class TestRepoGeneration(unittest.TestCase):
helper_path = Path('tests', 'test_helpers') helper_path = Path('tests', 'test_helpers')
addon_path = helper_path / 'addons' addon_path = helper_path / 'addons'
@@ -20,21 +20,6 @@ class test_make_repo(unittest.TestCase):
with self.assertRaises(FileNotFoundError): with self.assertRaises(FileNotFoundError):
make_repo.extract_blinfo(self.addon_path / test_file) make_repo.extract_blinfo(self.addon_path / test_file)
# def test_make_repo_valid(self):
# reference_repo = make_repo.make_repo(self.helper_path / 'addons', "test repo")
# repojson = Path.cwd() / 'repo.json'
# reference_repojson = self.helper_path / 'repo.json'
#
# try:
# with repojson.open('r') as repolist_f:
# with reference_repojson.open('r') as ref_repolist_f:
# repolist = json.loads(repolist_f.read())
# ref_repolist = json.loads(ref_repolist_f.read())
# self.assertEqual(repolist, ref_repolist)
# finally:
# # repojson.unlink()
# pass
def test_package_quantity(self): def test_package_quantity(self):
repo = make_repo.make_repo(self.addon_path, "name of the repo") repo = make_repo.make_repo(self.addon_path, "name of the repo")
acceptible_addons = [ acceptible_addons = [
@@ -49,12 +34,12 @@ class test_make_repo(unittest.TestCase):
# addons which should contain bl_infos # addons which should contain bl_infos
yes_blinfo = [ yes_blinfo = [
f for f in test_make_repo.addon_path.iterdir() f for f in TestRepoGeneration.addon_path.iterdir()
if not f.match('*nonaddon*') and not f.match('*invalid_addon*') if not f.match('*nonaddon*') and not f.match('*invalid_addon*')
] ]
# addons which should throw BadAddon because they have no blinfo # addons which should throw BadAddon because they have no blinfo
no_blinfo = [ no_blinfo = [
f for f in test_make_repo.addon_path.iterdir() f for f in TestRepoGeneration.addon_path.iterdir()
if f.match('*nonaddon*') if f.match('*nonaddon*')
] ]
@@ -81,6 +66,6 @@ def add_generated_tests(test_generator, params, destclass):
test_func = test_generator(param) test_func = test_generator(param)
setattr(destclass, 'test_{}'.format(param), test_func) setattr(destclass, 'test_{}'.format(param), test_func)
add_generated_tests(generate_good_blinfo_test, yes_blinfo, test_make_repo) add_generated_tests(generate_good_blinfo_test, yes_blinfo, TestRepoGeneration)
add_generated_tests(generate_bad_blinfo_test, no_blinfo, test_make_repo) add_generated_tests(generate_bad_blinfo_test, no_blinfo, TestRepoGeneration)