This repository has been archived on 2023-02-07. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-package-manager-addon/make_repo.py

163 lines
4.8 KiB
Python
Raw Normal View History

#!/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()