jellyfin-kodi/build.py

149 lines
4.5 KiB
Python
Executable file

# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
import os
import zipfile
from fnmatch import fnmatchcase as fnmatch
import logging
from datetime import datetime
import yaml
import click
import click_log
from bs4 import BeautifulSoup, Tag
logger = logging.getLogger("build")
click_log.basic_config(logger)
def zip_items(file_name, items, base_path=None):
if base_path is None:
base_path = os.path.splitext(file_name)[0]
elif not base_path:
base_path = ""
with zipfile.ZipFile(file_name, "w", zipfile.ZIP_DEFLATED) as z:
for item, path, _pattern in items:
logger.info(path)
archive_path = os.path.join(base_path, path)
if path not in ["."]:
z.write(item, archive_path)
return file_name
def format_changelog(version, changelog):
return 'v{addon_version} ({date}):\n{changelog}'.format(
addon_version=version,
date=datetime.today().strftime('%Y-%m-%d'),
changelog=changelog
)
def create_addon_xml(config, py3=True, template='template.xml', output='addon.xml'):
with open(template) as fh:
soup = BeautifulSoup(fh, features='xml')
pyversion = 'py3' if py3 else 'py2'
addon = soup.select_one('addon')
addon['id'] = config.get('id')
addon['name'] = config.get('name')
addon['version'] = "{}+{}".format(config.get('version'), pyversion)
addon['provider-name'] = config.get('provider')
dependencies = config.get('dependencies', {}).get(pyversion, [])
xml_deps = addon.select_one('requires') # type: Tag
for dep in dependencies:
tag = soup.new_tag('import', attrs=dep)
xml_deps.append(tag)
xml_news = addon.select_one("extension[point='xbmc.addon.metadata'] > news") # type: Tag
xml_news.string = format_changelog(config.get('version'), config.get('changelog'))
soup.smooth()
with open(output, 'w', encoding='utf-8') as fh:
logging.debug(soup)
fh.write(soup.prettify())
def get_config(filename='release.yaml'):
with open(filename, 'r') as fh:
return yaml.safe_load(fh)
def match_item(item, include, exclude):
for pattern in exclude:
if fnmatch(item, pattern):
logger.debug('Excluded: {!r} {!r}'.format(pattern, item))
return False, pattern
for pattern in include:
if fnmatch(item, pattern):
logger.debug('Included: {!r} {!r}'.format(pattern, item))
return True, pattern
return None, None
def get_items(include, exclude, basepath='.'):
for base, dirs, files in os.walk(basepath):
for dir_name in dirs:
dir_path = os.path.join(base, dir_name)
normalized_dir_path = os.path.normpath(os.path.relpath(dir_path, basepath))
match, pattern = match_item(normalized_dir_path, include, exclude)
if match:
yield dir_path, normalized_dir_path, pattern
for file_name in files:
file_path = os.path.join(base, file_name)
normalized_file_path = os.path.normpath(os.path.relpath(file_path, basepath))
match, pattern = match_item(normalized_file_path, include, exclude)
if match:
yield file_path, normalized_file_path, pattern
def build_filename(config, py3):
# type: (dict, bool) -> str
return "{}-{}+{}.zip".format(
config.get('id'),
config.get('version', '0.0.0'),
'py3' if py3 else 'py2'
)
@click.command()
@click.option('-3/-2', '--py3/--py2', default=True, type=bool, help="Default is Python 3.")
@click.option('--source', default='.', type=click.Path(exists=True, file_okay=False), help="Path to addon sources (current_dir).")
@click.option('--output', default=None, type=click.Path(), help="Output file (current_dir/addon-version+py3.zip).")
@click_log.simple_verbosity_option(logger)
def main(py3, source, output):
config = get_config(os.path.join(source, 'release.yaml'))
config_build = config.get('build', {})
include = config_build.get('include', [])
exclude = config_build.get('exclude', [])
if output is None:
output = build_filename(config, py3)
create_addon_xml(
config,
py3=py3,
template=os.path.join(source, 'template.xml'),
output=os.path.join(source, 'addon.xml')
)
items = get_items(include, exclude, source)
zip_file_name = zip_items(output, items, base_path=config.get('id'))
click.echo(zip_file_name)
if __name__ == "__main__":
main()