Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve addon report infrastructure #64

Merged
merged 9 commits into from
Apr 9, 2018
Merged
13 changes: 11 additions & 2 deletions kodi_addon_checker/__main__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import os
import argparse
import os

from kodi_addon_checker.check_repo import check_repo
from kodi_addon_checker.common import load_plugins
from kodi_addon_checker.config import ConfigManager, Config


def dir_type(dir_path):
Expand All @@ -27,6 +30,7 @@ def dir_type(dir_path):
def main():
"""The entry point to kodi-addon-checker
"""
load_plugins()
parser = argparse.ArgumentParser(prog="kodi-addon-checker",
description="Checks Kodi repo for best practices and creates \
problem and warning reports.\r\nIf optional add-on \
Expand All @@ -37,8 +41,13 @@ def main():
version="%(prog)s 0.0.1")
parser.add_argument("add_on", metavar="add-on", type=dir_type, nargs="*",
help="optional add-on directories")
ConfigManager.fill_cmd_args(parser)
args = parser.parse_args()
check_repo(os.path.abspath(os.getcwd()), args.add_on)

repo_path = os.path.abspath(os.getcwd())
config = Config(repo_path, args)
ConfigManager.process_config(config)
check_repo(config, repo_path, args.add_on)


if __name__ == "__main__":
Expand Down
226 changes: 87 additions & 139 deletions kodi_addon_checker/check_addon.py

Large diffs are not rendered by default.

36 changes: 15 additions & 21 deletions kodi_addon_checker/check_repo.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
import os
import sys
import json

import kodi_addon_checker.check_addon as check_addon
from kodi_addon_checker.common import colorPrint
from kodi_addon_checker.report import Report, INFORMATION, Record, ReportManager


def _read_config_for_version(repo_path):
config_path = os.path.join(repo_path, '.tests-config.json')
if os.path.isfile(config_path):
with open(config_path) as json_data:
return json.load(json_data)

return None


def check_repo(repo_path, parameters):
error_counter = {"warnings": 0, "problems": 0}
def check_repo(config, repo_path, parameters):
repo_report = Report()
repo_report.add(Record(INFORMATION, "Checking repository %s" % repo_path))
print("Repo path " + repo_path)

This comment was marked as spam.

This comment was marked as spam.

if len(parameters) == 0:
toplevel_folders = sorted(next(os.walk(repo_path))[1])
Expand All @@ -24,21 +17,22 @@ def check_repo(repo_path, parameters):

print("Toplevel folders " + str(toplevel_folders))

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.


config = _read_config_for_version(repo_path)

for addon_folder in toplevel_folders:
if addon_folder[0] != '.':
repo_report.add(Record(INFORMATION, "Checking add-on %s" % addon_folder))
addon_path = os.path.join(repo_path, addon_folder)
error_counter = check_addon.start(
error_counter, addon_path, config)
addon_report = check_addon.start(addon_path, config)
repo_report.add(addon_report)

ReportManager.report(repo_report)

if error_counter["problems"] > 0:
if repo_report.problem_count > 0:
colorPrint("We found %s problems and %s warnings, please check the logfile." % (

This comment was marked as spam.

This comment was marked as spam.

error_counter["problems"], error_counter["warnings"]), "31")
repo_report.problem_count, repo_report.warning_count), "31")
sys.exit(1)
elif error_counter["warnings"] > 0:
# If we only found warnings, don't mark the build as broken
elif repo_report.warning_count > 0:
# If we only found warning, don't mark the build as broken
colorPrint("We found %s problems and %s warnings, please check the logfile." % (

This comment was marked as spam.

This comment was marked as spam.

error_counter["problems"], error_counter["warnings"]), "35")
repo_report.problem_count, repo_report.warning_count), "35")

print("Finished!")

This comment was marked as spam.

This comment was marked as spam.

23 changes: 14 additions & 9 deletions kodi_addon_checker/common.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
def colorPrint(string, color):
print("\033[%sm%s\033[0m" % (color, string))
import importlib
import os
import pkgutil
import sys


def check_config(config, value):
if config is None:
return False
elif value in config and config[value]:
return True
else:
return False
def colorPrint(string, color):

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

print("\033[%sm%s\033[0m" % (color, string))


def has_transparency(im):
Expand All @@ -24,3 +21,11 @@ def has_transparency(im):
return False
except StopIteration:
return False


def load_plugins():
plugins_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "plugins")
sys.path.append(plugins_dir)
for importer, package_name, _ in pkgutil.iter_modules([plugins_dir]):
if "test_" not in package_name:
importlib.import_module(package_name)
51 changes: 51 additions & 0 deletions kodi_addon_checker/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import json
import os
from argparse import ArgumentParser

from kodi_addon_checker.report import ReportManager


class Config(object):
def __init__(self, repo_path, cmd_args=None):
self.configs = {} if cmd_args is None else vars(cmd_args)
self._load_config(repo_path)

def _load_config(self, repo_path):
config_path = os.path.join(repo_path, '.tests-config.json')
if os.path.isfile(config_path):
with open(config_path) as json_data:
file_config = json.load(json_data)
if file_config is not None:
for key, value in self.configs.items():
if value is not None:
file_config[key] = value
self.configs = file_config

def is_enabled(self, value):
return self.configs.get(value, False)

def __getitem__(self, item):
return self.configs.get(item)


class ConfigManager(object):
configurations = {}

@classmethod
def register(cls, config, description, default_value, action):
cls.configurations[config] = [description, default_value, action]

@classmethod
def fill_cmd_args(cls, parser: ArgumentParser):
# Add --reporter
parser.add_argument("--reporter", action="append", choices=list(ReportManager.reporters.keys()),
help="""enable a reporter with the given name.

This comment was marked as spam.

This comment was marked as spam.

You can use this option multiple times to enable more than one reporters""")

@classmethod
def process_config(cls, config):
print(config.configs)
reporters = config["reporter"]
if reporters is not None:
# To disable all, pass empty array in .tests-config.json
ReportManager.enable(reporters)
Empty file.
19 changes: 19 additions & 0 deletions kodi_addon_checker/plugins/console_reporter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from kodi_addon_checker.common import colorPrint

from kodi_addon_checker.report import Reporter, Report, Record, reporter, INFORMATION, WARNING, PROBLEM


@reporter(name="console", enabled=True)
class ConsoleReporter(Reporter):

def report(self, report: Report):
if type(report) is Record:
if report.log_level == INFORMATION:
colorPrint(report, "34")
elif report.log_level == WARNING:
colorPrint(report, "35")
elif report.log_level == PROBLEM:
colorPrint(report, "31")
else:
for rep in report:
self.report(rep)
105 changes: 105 additions & 0 deletions kodi_addon_checker/report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import inspect
from abc import ABC

PROBLEM = "ERROR"
WARNING = "WARN"
INFORMATION = "INFO"


class Report(object):
def __init__(self):
self.problem_count = 0
self.warning_count = 0
self.information_count = 0
self.reports = []

def add(self, report):
self.reports.append(report)
if type(report) is Record:
if PROBLEM == report.log_level:
self.problem_count += 1
elif WARNING == report.log_level:
self.warning_count += 1
else:
self.information_count += 1
else:
self.problem_count += report.problem_count
self.warning_count += report.warning_count
self.information_count += report.information_count

def __iter__(self):
return iter(self.reports)


class Record(Report):
def __init__(self, log_level, message, start_line=-1, end_line=-1, start_char_position=-1,

This comment was marked as spam.

This comment was marked as spam.

end_char_position=-1):
self.log_level = log_level
self.message = message
self.start_line = start_line

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

self.end_line = end_line
self.start_char_position = start_char_position
self.end_char_position = end_char_position

def add(self, record):
pass

def __str__(self):
"""
Text representation of record.
:return: text representation of report
"""
if self.start_line == -1:
return "%s: %s" % (self.log_level, self.message)
elif self.start_char_position == -1:
return "%s [%d-%d]: %s" % (self.log_level, self.start_line, self.end_line, self.message)
else:
return "%s [%d:%d-%d:%d]: %s" % (
self.log_level, self.start_line, self.start_char_position, self.end_line, self.end_char_position,
self.message)


class Reporter(ABC):

def report(self, report: Report):
pass


class ReportManager(object):
reporters = {}

@classmethod
def register(cls, reporter_clazz: Reporter, name, enabled):
cls.reporters[name] = [reporter_clazz, enabled]

@classmethod
def enable(cls, names):
"""
Enable only the given list of names and disable the rest.
:param names: list of reporter names
:return: None
"""
for name, arr in cls.reporters.items():
arr[1] = name in names

@classmethod
def report(cls, report: Report):
for arr in cls.reporters.values():
if arr[1]:
# Report enabled
arr[0]().report(report)


def reporter(name, enabled=False):
def _reporter(clazz):
if inspect.isclass(clazz):
if not hasattr(clazz, "report") or len(inspect.signature(getattr(clazz, "report")).parameters.items()) != 2:
raise RuntimeError("Reporter must have a function 'report(self, report: Report)")
else:
raise RuntimeError("Reporter must be a class")

# Register the reporter
ReportManager.register(clazz, name, enabled)
return clazz

return _reporter
32 changes: 12 additions & 20 deletions tests/test_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,13 @@
#

import os
import json
import pytest
from PIL import Image
from kodi_addon_checker.common import check_config, has_transparency

FIXTURE_PATH = os.path.join("tests", "fixtures")

from PIL import Image

def __read_config_for_version(filename):
config_path = os.path.join(FIXTURE_PATH, filename)
if os.path.isfile(config_path):
with open(config_path) as json_data:
return json.load(json_data)
from kodi_addon_checker.common import has_transparency
from kodi_addon_checker.config import Config

return None
FIXTURE_PATH = os.path.join("tests", "fixtures")


def __load_image(filename):
Expand All @@ -25,23 +17,23 @@ def __load_image(filename):


def test_if_false_gets_picked_up():
config = __read_config_for_version('.tests-config.json')
assert check_config(config, "check_license_file_exists") is False
config = Config(FIXTURE_PATH)
assert config.is_enabled("check_license_file_exists") is False


def test_if_true_gets_picked_up():
config = __read_config_for_version('.tests-config.json')
assert check_config(config, "check_legacy_strings_xml") is True
config = Config(FIXTURE_PATH)
assert config.is_enabled("check_legacy_strings_xml") is True


def test_if_does_not_exists_default_to_false():
config = __read_config_for_version('.tests-config.json')
assert check_config(config, "does_not_exist") is False
config = Config(FIXTURE_PATH)
assert config.is_enabled("does_not_exist") is False


def test_with_missing_config():
config = __read_config_for_version('does_not_exist.json')
assert check_config(config, "does_not_exist") is False
config = Config('does_not_exist')
assert config.is_enabled("does_not_exist") is False


def test_has_transparency_rgb():
Expand Down