Cleanup: Changes suggested by @sybren
This commit is contained in:
126
make_repo.py
126
make_repo.py
@@ -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)
|
||||||
|
|
||||||
|
@@ -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)
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user