#!/usr/bin/env python import argparse import os import xml.etree.ElementTree as ET import zipfile from datetime import datetime from pathlib import Path import yaml def indent(elem: ET.Element, level: int = 0) -> None: """ Nicely formats output xml with newlines and spaces https://stackoverflow.com/a/33956544 """ i = "\n" + level * " " if len(elem): if not elem.text or not elem.text.strip(): elem.text = i + " " if not elem.tail or not elem.tail.strip(): elem.tail = i for elem in elem: indent(elem, level + 1) if not elem.tail or not elem.tail.strip(): elem.tail = i else: if level and (not elem.tail or not elem.tail.strip()): elem.tail = i def create_addon_xml(config: dict, source: str, py_version: str) -> None: """ Create addon.xml from template file """ # Load template file with open('{}/.config/template.xml'.format(source), 'r') as f: tree = ET.parse(f) root = tree.getroot() # Populate dependencies in template dependencies = config['dependencies'].get(py_version) for dep in dependencies: ET.SubElement(root.find('requires'), 'import', attrib=dep) # Populate version string addon_version = config.get('version') root.attrib['version'] = '{}+{}'.format(addon_version, py_version) # Populate Changelog date = datetime.today().strftime('%Y-%m-%d') changelog = config.get('changelog') for section in root.findall('extension'): news = section.findall('news') if news: news[0].text = 'v{} ({}):\n{}'.format(addon_version, date, changelog) # Format xml tree indent(root) # Write addon.xml tree.write('{}/addon.xml'.format(source), encoding='utf-8', xml_declaration=True) def zip_files(py_version: str, source: str, target: str) -> None: """ Create installable addon zip archive """ archive_name = 'plugin.video.jellyfin+{}.zip'.format(py_version) with zipfile.ZipFile('{}/{}'.format(target, archive_name), 'w') as z: for root, dirs, files in os.walk(args.source): for filename in filter(file_filter, files): file_path = os.path.join(root, filename) if folder_filter(file_path): relative_path = os.path.join('plugin.video.jellyfin', os.path.relpath(file_path, source)) z.write(file_path, relative_path) def file_filter(file_name: str) -> bool: """ True if file_name is meant to be included """ return 'plugin.video.jellyfin' not in file_name and 'pyo' not in file_name def folder_filter(folder_name: str) -> bool: """ True if folder_name is meant to be included """ return ('.ci' not in folder_name and '.git' not in folder_name and '.github' not in folder_name and '.config' not in folder_name) if __name__ == '__main__': parser = argparse.ArgumentParser(description='Build flags:') parser.add_argument( '--version', type=str, choices=('py2', 'py3'), default='py3') parser.add_argument( '--source', type=Path, default=Path(__file__).absolute().parent) parser.add_argument( '--target', type=Path, default=Path(__file__).absolute().parent) args = parser.parse_args() # Load config file config_path = os.path.join(args.source, 'release.yaml') with open(config_path, 'r') as fh: release_config = yaml.safe_load(fh) create_addon_xml(release_config, args.source, args.version) zip_files(args.version, args.source, args.target)