#!/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("{}/.build/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, dev: bool) -> 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 dev or 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 ( not ( file_name.startswith("plugin.video.jellyfin") and file_name.endswith(".zip") ) and not file_name.endswith(".pyo") and not file_name.endswith(".pyc") and not file_name.endswith(".pyd") ) def folder_filter(folder_name: str) -> bool: """ True if folder_name is meant to be included """ filters = [ ".ci", ".git", ".github", ".build", ".mypy_cache", ".pytest_cache", "__pycache__", ] for f in filters: if f in folder_name.split(os.path.sep): return False return True 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) parser.add_argument("--dev", dest="dev", action="store_true") parser.set_defaults(dev=False) 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, args.dev)