Improve make_repo.py

This cleans up make_repo.py a bit, using file extensions to determine
file type.

This also loosens the testing repo generation, as the existing
test required matching a predifed expected output which had to be
updated on every change (essentially making it a moot test, as the
reference output was obtained from the functions output).
The new test just checks if the output has the same number of packages
as the input dir has addons.

Tips on how best to test these sorts of "higher level" functions (if at
all) would be welcome :)
This commit is contained in:
gandalf3
2017-07-04 23:56:19 -07:00
parent 12533268fc
commit 3847cc877f
4 changed files with 880 additions and 70 deletions

View File

@@ -54,86 +54,120 @@ def extract_blinfo(item: Path) -> dict:
"""
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())
f = (item / '__init__.py').open("r")
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
with f:
blinfo = parse_blinfo(f.read())
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)
# 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:
# If it's not a valid zip, assume file is just a normal file
ext = item.suffix.lower()
if ext == '.zip':
try:
with item.open() as f:
blinfo = parse_blinfo(f.read())
except: #HACK
# If it's not a zip and its not parse-able python, then it's not an addon
raise BadAddon("File '%s' doesn't appear to be an addon" % 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':
with item.open() as f:
blinfo = parse_blinfo(f.read())
else:
raise BadAddon("File '%s' doesn't have a .zip or .py extension; not an addon" % item)
# This should not happen
if blinfo == None:
raise RuntimeError("Could not read addon '%s'" % addon_name)
if blinfo is None:
raise RuntimeError("Could not read addon '%s'" % item.name)
return blinfo
class Package:
def __init__(self, path: Path, bl_info: dict, baseurl=None):
self.bl_info = bl_info
self.path = path
self.url = None
def make_repo(path: Path):
def dict(self) -> dict:
return {
'bl_info': self.bl_info,
'url': self.url,
}
class Repository:
def __init__(self, name: str):
self.name = name
self.url = None
self.packages = []
def add_package(self, pkg: Package):
# if pkg.url is None:
# pkg.url =
self.packages.append(pkg)
def dict(self) -> dict:
return {
'name': self.name,
'packages': [p.dict() for p in self.packages],
'url': self.url,
}
def dump(self, path: Path):
with (path / 'repo.json').open('w', encoding='utf-8') as repo_file:
json.dump(self.dict(), repo_file, indent=4, sort_keys=True)
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:
"""Make repo.json for files in directory 'path'"""
repo_data = {}
package_data = []
repo = Repository(name)
if not path.is_dir():
raise FileNotFoundError(path)
for addon, bl_info in iter_addons(path):
package_datum = {}
# Check if we have all bl_info fields we want
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
package = Package(addon, bl_info)
repo.add_package(package)
log.info("Repository generation successful")
cwd = Path.cwd()
dump_repo(cwd, repo_data)
log.info("repo.json written to %s" % cwd)
return repo
def main():
@@ -143,18 +177,25 @@ def main():
help="Increase verbosity (can be used multiple times)",
action="count",
default=0)
parser.add_argument('-n', '--name',
help="Name of repo (defaults to basename of 'path')")
parser.add_argument('-o', '--output',
help="Directory in which to write repo.json file",
type=Path,
default=Path.cwd())
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')
if args.name is None:
args.name = args.path.name
make_repo(args.path)
logging.basicConfig(format='%(levelname)8s: %(message)s', level=logging.INFO)
log.level += args.verbose
repo = make_repo(args.path, args.name)
repo.dump(args.output)
if __name__ == '__main__':
main()