diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..aaf780b --- /dev/null +++ b/.flake8 @@ -0,0 +1,4 @@ +[flake8] +per-file-ignores = __init__.py:F401 +extend-exclude = build +extend-ignore = E501 diff --git a/auswertung.code-workspace b/auswertung.code-workspace index f7266e4..9daad0d 100644 --- a/auswertung.code-workspace +++ b/auswertung.code-workspace @@ -13,6 +13,9 @@ "python.testing.pytestEnabled": false, "python.autoComplete.extraPaths": [ "${workspaceFolder:code}/venv/lib" - ] + ], + "editor.formatOnSave": true, + "editor.renderWhitespace": "all", + "python.formatting.provider": "black", } } diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..3000714 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[too.black] + +line-length = 120 diff --git a/requiremnts.txt b/requiremnts.txt index 623b37c..ce97304 100644 --- a/requiremnts.txt +++ b/requiremnts.txt @@ -1,5 +1,6 @@ attrs==22.1.0 beautifulsoup4==4.11.1 +black==23.11.0 blinker==1.6.2 certifi==2023.7.22 charset-normalizer==3.2.0 @@ -17,8 +18,13 @@ iniconfig==1.1.1 itsdangerous==2.1.2 Jinja2==3.1.2 MarkupSafe==2.1.3 -packaging==21.3 +mypy-extensions==1.0.0 +packaging==23.2 +pathspec==0.11.2 +platformdirs==4.0.0 pluggy==1.0.0 +pydocstyle==6.3.0 +pyflakes==3.1.0 pynsist==2.8 pyparsing==3.0.9 pytest==7.2.0 @@ -26,6 +32,7 @@ pytest-cov==4.0.0 pytest-mock==3.10.0 requests==2.31.0 requests_download==0.1.2 +snowballstemmer==2.2.0 soupsieve==2.3.2.post1 tabulate==0.9.0 tomli==2.0.1 diff --git a/run-all-examples.sh b/run-all-examples.sh new file mode 100755 index 0000000..cf2a5a6 --- /dev/null +++ b/run-all-examples.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +if [ $# -ne 1 ] +then + echo "Please give path of test cases as parameter." + exit 1 +fi + +rundir=$(realpath "$(dirname "$0")") + +cd "$1" + +for i in Turnier\ * +do + echo "Running on data in $i" + + if [ ! -r "$i/result.table" ] + then + echo "No result file is found. Skipping." + continue + fi + + tmp=$(mktemp) + "$rundir/solo_runner.sh" --no-flask -a "$i/HTML" > "$tmp" + + if diff -u "$i/result.table" "$tmp" > /dev/null + then + rm "$tmp" + else + echo "Differences found in competition $i" + mv "$tmp" "$i/result2.table" + fi + +done diff --git a/src/main.py b/src/main.py index fcff5d8..90fcbd9 100644 --- a/src/main.py +++ b/src/main.py @@ -2,15 +2,17 @@ import solo_turnier import logging import coloredlogs + def __initLogging(): logging.basicConfig() logging.root.setLevel(logging.NOTSET) - logger = logging.getLogger('solo_turnier') - + logger = logging.getLogger("solo_turnier") + coloredlogs.install(level=5, logger=logger) return logger + def main(): l = __initLogging() cli = solo_turnier.cli.Cli(l) @@ -18,14 +20,14 @@ def main(): batchWorker = solo_turnier.batch.BatchWorker(cli) if cli.showGUI(): - raise Exception('Not yet implemented') + raise Exception("Not yet implemented") elif cli.startFlaskServer(): solo_turnier.flask.startFlask( - batchWorker, + batchWorker, debug=cli.getLogLevel() > 0, port=cli.getPort(), showOnlyFinalists=not cli.showAllParticipants(), - externalDebugger=cli.externalDebugger + externalDebugger=cli.externalDebugger, ) else: combinedData = batchWorker.run( @@ -35,5 +37,6 @@ def main(): consoleOutputtter = solo_turnier.output.ConsoleOutputter() consoleOutputtter.output(combinedData) -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/src/solo_turnier/__init__.py b/src/solo_turnier/__init__.py index e06c62f..1446c3e 100644 --- a/src/solo_turnier/__init__.py +++ b/src/solo_turnier/__init__.py @@ -1,10 +1,8 @@ - from . import competition_class from . import group from . import types from . import cli -from . import reader from . import participant from . import html_locator @@ -14,3 +12,5 @@ from . import output from . import batch from . import flask + +from . import workers diff --git a/src/solo_turnier/batch.py b/src/solo_turnier/batch.py index 99de611..a2142d2 100644 --- a/src/solo_turnier/batch.py +++ b/src/solo_turnier/batch.py @@ -1,4 +1,3 @@ - import solo_turnier import logging import os @@ -6,29 +5,33 @@ import pprint import tabulate + class BatchWorker: - def __init__( - self, - config: solo_turnier.cli.Cli - ): - self.l = logging.getLogger('solo_turnier.batch') + def __init__(self, config: solo_turnier.cli.Cli): + self.l = logging.getLogger("solo_turnier.batch") self.config = config - def run(self, removeFilteredParicipants=True): + def run(self, removeFilteredParicipants=True) -> solo_turnier.types.Stage5: self.l.debug(self.config.__dict__) - + locator = solo_turnier.html_locator.HtmlLocator() - self.l.info('Checking for feasible preview HTML export files in "%s"', self.config.importHtmlPath()) - htmlCandidatesPreview = locator.findPreviewRoundCandidates(self.config.importHtmlPath()) - self.l.debug('Found HTML file candidates for preview rounds: %s', htmlCandidatesPreview) + self.l.info( + 'Checking for feasible HTML export files in "%s"', + self.config.importHtmlPath(), + ) htmlResultFiles = locator.findCandidates(self.config.importHtmlPath()) - self.l.debug('Using HTML result files for result extraction: %s', htmlResultFiles) + self.l.debug( + "Using HTML result files for result extraction: %s", htmlResultFiles + ) - worker = solo_turnier.worker.Worker() - importedData = worker.collectAllData(htmlCandidatesPreview, htmlResultFiles) + worker = solo_turnier.workers.Worker.Worker() + importedData = worker.collectAllData(htmlResultFiles) combinedData = worker.combineData(importedData) worker.filterOutFinalists(combinedData, removeFilteredParicipants) - return combinedData + outputShaper = solo_turnier.workers.OutputShaper.OutputShaper() + shapedData = outputShaper.shapeResults(combinedData) + + return shapedData diff --git a/src/solo_turnier/cli.py b/src/solo_turnier/cli.py index b190663..d4c256f 100644 --- a/src/solo_turnier/cli.py +++ b/src/solo_turnier/cli.py @@ -3,27 +3,58 @@ import logging import debugpy + class Cli: def __init__(self, l: logging.Logger): parser = argparse.ArgumentParser() # parser.add_argument('--gui', help='Show the GUI', action='store_true') - parser.add_argument('--no-flask', action='store_false', dest='flask', help='Disable the internal flask web server') - parser.add_argument('--port', help='The port to listen for incoming requests', default='8082') + parser.add_argument( + "--no-flask", + action="store_false", + dest="flask", + help="Disable the internal flask web server", + ) + parser.add_argument( + "--port", help="The port to listen for incoming requests", default="8082" + ) - parser.add_argument('html', help='The path from where to look for HTML export files', nargs=1, default=['.']) - parser.add_argument('-o', '--output', help='Set the output path of the script', nargs=1, default=[None]) - parser.add_argument('--all-participants', '-a', action='store_true', help='Show all participants not only finalists') + parser.add_argument( + "html", + help="The path from where to look for HTML export files", + nargs=1, + default=["."], + ) + parser.add_argument( + "-o", + "--output", + help="Set the output path of the script", + nargs=1, + default=[None], + ) + parser.add_argument( + "--all-participants", + "-a", + action="store_true", + help="Show all participants not only finalists", + ) - parser.add_argument('-v', '--verbose', help='Increase verbosity', action='count', default=0) - parser.add_argument('-d', '--debug', action='store_true', help='Activate debugging during startup') + parser.add_argument( + "-v", "--verbose", help="Increase verbosity", action="count", default=0 + ) + parser.add_argument( + "-d", + "--debug", + action="store_true", + help="Activate debugging during startup", + ) self.__args = parser.parse_args() if self.__args.debug: debugpy.listen(5678) debugpy.wait_for_client() - + self.externalDebugger = self.__args.debug - + map = { 0: logging.ERROR, 1: logging.WARN, @@ -33,28 +64,25 @@ class Cli: } logLevel = map.get(self.__args.verbose, logging.DEBUG) l.setLevel(logLevel) - + def showGUI(self): # return self.__args.gui return False - + def startFlaskServer(self): return self.__args.flask def importHtmlPath(self): return self.__args.html[0] - - def importCSVPath(self): - return self.__args.import_from[0] - + def output(self): return self.__args.output[0] - + def getLogLevel(self): return self.__args.verbose - + def showAllParticipants(self): return self.__args.all_participants - + def getPort(self): return int(self.__args.port) diff --git a/src/solo_turnier/competition_class.py b/src/solo_turnier/competition_class.py index 20e1107..019baae 100644 --- a/src/solo_turnier/competition_class.py +++ b/src/solo_turnier/competition_class.py @@ -1,74 +1,86 @@ import re + class CompetitionClass: def __init__(self, text: str): self.name = text - + def __repr__(self): - return self.name + return f"{self.name}" + class CombinedCompetitionClass: - def __init__(self, clsA: CompetitionClass, clsB: CompetitionClass, clsC: CompetitionClass = None): + def __init__( + self, + clsA: CompetitionClass, + clsB: CompetitionClass, + clsC: CompetitionClass = None, + ): self.clsA = clsA self.clsB = clsB self.clsC = clsC - + def __repr__(self): if self.clsC is None: - return f'{self.clsA}/{self.clsB}' + return f"{self.clsA}/{self.clsB}" else: - return f'{self.clsA}/{self.clsB}/{self.clsC}' + return f"{self.clsA}/{self.clsB}/{self.clsC}" + + def __eq__(self, other): + return type(self) == type(other) and self.__dict__ == other.__dict__ + + def __hash__(self): + return hash(("combinedClass", self.clsA, self.clsB, self.clsC)) + Class_t = CompetitionClass | CombinedCompetitionClass + class CompetitionClassParser: - NEWC = CompetitionClass('Newc.') - BEG = CompetitionClass('Beg.') - ADV = CompetitionClass('Adv.') + NEWC = CompetitionClass("Newc.") + BEG = CompetitionClass("Beg.") + ADV = CompetitionClass("Adv.") + + PREVIEW = CompetitionClass("Sichtung") - PREVIEW = CompetitionClass('Sichtung') - def __init__(self): - self.mapNames = { - 'Newc': self.NEWC, - 'Newc.': self.NEWC, - 'Newcomer': self.NEWC, - 'Beg': self.BEG, - 'Beg.': self.BEG, - 'Beginner': self.BEG, - 'Adv': self.ADV, - 'Adv.': self.ADV, - 'Advanced': self.ADV, + "Newc": self.NEWC, + "Newc.": self.NEWC, + "Newcomer": self.NEWC, + "Beg": self.BEG, + "Beg.": self.BEG, + "Beginner": self.BEG, + "Adv": self.ADV, + "Adv.": self.ADV, + "Advanced": self.ADV, } - self.namesPreview = [ - 'Sichtung' - ] + self.namesPreview = ["Sichtung"] self.mapShortNames = { - 'N': self.NEWC, - 'B': self.BEG, - 'A': self.ADV, + "N": self.NEWC, + "B": self.BEG, + "A": self.ADV, } def parseClass(self, cls: str, allowPreview: bool = False) -> Class_t: if allowPreview and cls in self.namesPreview: - return self.PREVIEW - - match = re.compile('^(\\w+\\.?)/(\\w+\\.?)$').match(cls) + return self.PREVIEW + + match = re.compile("^(\\w+\\.?)/(\\w+\\.?)$").match(cls) if match is not None: clsA = self.mapNames[match.group(1)] clsB = self.mapNames[match.group(2)] return CombinedCompetitionClass(clsA, clsB) else: return self.mapNames[cls] - + def parseAbbreviatedClass(self, cls: str) -> Class_t: return self.mapShortNames[cls] - + def isPureClass(self, cls: str, allowPreview: bool = False) -> bool: parsedClass = self.parseClass(cls, allowPreview) return isinstance(parsedClass, CompetitionClass) - + def getAllClasses(self) -> list[CompetitionClass]: return [self.NEWC, self.BEG, self.ADV] diff --git a/src/solo_turnier/flask.py b/src/solo_turnier/flask.py index ab5723c..5864a50 100644 --- a/src/solo_turnier/flask.py +++ b/src/solo_turnier/flask.py @@ -4,29 +4,29 @@ import logging _l = logging.getLogger(__name__) + def startFlask( batchWorker: solo_turnier.batch.BatchWorker, debug: bool = False, port: int = 8082, showOnlyFinalists: bool = True, externalDebugger: bool = False, - ): +): app = flask.Flask(__name__) - @app.route('/') + @app.route("/") def index(): combinedData = batchWorker.run(False) - _l.debug('Show only finalists %s', showOnlyFinalists) + _l.debug("Show only finalists %s", showOnlyFinalists) - return flask.render_template('index.html', data=combinedData, onlyFinalists=showOnlyFinalists) - - @app.get('/custom.css') - def css(): - ret = flask.render_template( - 'custom.css', - onlyFinalists=showOnlyFinalists + return flask.render_template( + "index.html", data=combinedData, onlyFinalists=showOnlyFinalists ) - return flask.Response(ret, mimetype='text/css') + + @app.get("/custom.css") + def css(): + ret = flask.render_template("custom.css", onlyFinalists=showOnlyFinalists) + return flask.Response(ret, mimetype="text/css") useReloader = debug and not externalDebugger - app.run(host='0.0.0.0', port=port, debug=debug, use_reloader=useReloader) + app.run(host="0.0.0.0", port=port, debug=debug, use_reloader=useReloader) diff --git a/src/solo_turnier/group.py b/src/solo_turnier/group.py index 7e0f13d..165b1a3 100644 --- a/src/solo_turnier/group.py +++ b/src/solo_turnier/group.py @@ -1,96 +1,109 @@ import re + class Group: def __init__(self, text: str): self.name = text - + def __repr__(self): - return self.name + return f"{self.name}" + + def getContainedGroups(self): + return (self,) + class CombinedGroup: def __init__(self, grpA: Group, grpB: Group): self.clsA = grpA self.clsB = grpB - + def __repr__(self): - return f'{self.clsA}/{self.clsB}' + return f"{self.clsA}/{self.clsB}" + + def __hash__(self): + return hash(("combinedGroup", self.clsA, self.clsB)) + + def __eq__(self, other): + return type(self) == type(other) and self.__hash__() == other.__hash__() + + def getContainedGroups(self): + return (self.clsA, self.clsB) + Group_t = Group | CombinedGroup + class GroupParser: - KIN = Group('Kin.') - JUN = Group('Jun.') - JUG = Group('Jug.') - HGR = Group('Hgr.') - MAS1 = Group('Mas. I') - MAS2 = Group('Mas. II') - MAS3 = Group('Mas. III') - MAS4 = Group('Mas. IV') - MAS5 = Group('Mas. V') + KIN = Group("Kin.") + JUN = Group("Jun.") + JUG = Group("Jug.") + HGR = Group("Hgr.") + MAS1 = Group("Mas. I") + MAS2 = Group("Mas. II") + MAS3 = Group("Mas. III") + MAS4 = Group("Mas. IV") + MAS5 = Group("Mas. V") def __init__(self): - self.mapNames = { - 'Kin': self.KIN, - 'Kin.': self.KIN, - 'Kinder': self.KIN, - - 'Jun': self.JUN, - 'Jun.': self.JUN, - 'Junioren': self.JUN, - - 'Jug': self.JUG, - 'Jug.': self.JUG, - 'Jugend': self.JUG, - - 'Hgr': self.HGR, - 'HGr': self.HGR, - 'Hgr.': self.HGR, - 'HGr.': self.HGR, - 'Hauptgruppe': self.HGR, - - 'Mas. I': self.MAS1, - 'Mas. II': self.MAS2, - 'Mas. III': self.MAS3, - 'Mas. IV': self.MAS4, - 'Mas. V': self.MAS5, - 'Mas I': self.MAS1, - 'Mas II': self.MAS2, - 'Mas III': self.MAS3, - 'Mas IV': self.MAS4, - 'Mas V': self.MAS5, - 'Masters I': self.MAS1, - 'Masters II': self.MAS2, - 'Masters III': self.MAS3, - 'Masters IV': self.MAS4, - 'Masters V': self.MAS5, + "Kin": self.KIN, + "Kin.": self.KIN, + "Kinder": self.KIN, + "Jun": self.JUN, + "Jun.": self.JUN, + "Junioren": self.JUN, + "Jug": self.JUG, + "Jug.": self.JUG, + "Jugend": self.JUG, + "Hgr": self.HGR, + "HGr": self.HGR, + "Hgr.": self.HGR, + "HGr.": self.HGR, + "Hauptgruppe": self.HGR, + "Mas. I": self.MAS1, + "Mas. II": self.MAS2, + "Mas. III": self.MAS3, + "Mas. IV": self.MAS4, + "Mas. V": self.MAS5, + "Mas I": self.MAS1, + "Mas II": self.MAS2, + "Mas III": self.MAS3, + "Mas IV": self.MAS4, + "Mas V": self.MAS5, + "Masters I": self.MAS1, + "Masters II": self.MAS2, + "Masters III": self.MAS3, + "Masters IV": self.MAS4, + "Masters V": self.MAS5, } - def parseClass(self, cls: str) -> Group_t: - match = re.compile('^(\\w+\\.?)/(\\w+\\.?)$').match(cls) + def parseGroup(self, cls: str) -> Group_t: + match = re.compile("^(\\w+\\.?)/(\\w+\\.?)$").match(cls) if match is not None: grpA = self.mapNames[match.group(1)] grpB = self.mapNames[match.group(2)] return CombinedGroup(grpA, grpB) else: return self.mapNames[cls] - - def isPureClass(self, cls: str) -> bool: - parsedClass = self.parseClass(cls) - return isinstance(parsedClass, Group) - + + def isPureGroup(self, cls: str) -> bool: + parsedGroup = self.parseGroup(cls) + return isinstance(parsedGroup, Group) + def getGroups(self) -> list[Group]: - return[ - GroupParser.KIN, - GroupParser.JUN, - GroupParser.JUG, - GroupParser.HGR, - GroupParser.MAS1, - GroupParser.MAS2, - GroupParser.MAS3, - GroupParser.MAS4, - GroupParser.MAS5 + return [ + GroupParser.KIN, + GroupParser.JUN, + GroupParser.JUG, + GroupParser.HGR, + GroupParser.MAS1, + GroupParser.MAS2, + GroupParser.MAS3, + GroupParser.MAS4, + GroupParser.MAS5, ] - + def getGroupsAsSortedList(self, groups) -> list[Group]: - return [x for x in self.getGroups() if x in groups] + mainGroups = [x for x in self.getGroups() if x in groups] + additionalGroups = set(groups).difference(mainGroups) + return mainGroups + list(additionalGroups) diff --git a/src/solo_turnier/html_locator.py b/src/solo_turnier/html_locator.py index 4adbe7d..95bd20b 100644 --- a/src/solo_turnier/html_locator.py +++ b/src/solo_turnier/html_locator.py @@ -1,36 +1,33 @@ import os import logging + class HtmlLocator: def __init__(self): - self.l = logging.getLogger('solo_turnier.html_locator') - + self.l = logging.getLogger("solo_turnier.html_locator") + def __findRecursivelyCandidates(self, path: str, fileName: str): ret = [] ls = os.listdir(path) - + if fileName in ls and os.path.isfile(os.path.join(path, fileName)): ret.append(os.path.join(path, fileName)) - + for p in ls: subPath = os.path.join(path, p) if os.path.isdir(subPath): ret = ret + self.__findRecursivelyCandidates(subPath, fileName) - + return ret def __fingMatchingTabs(self, ergCandidate): path = os.path.dirname(ergCandidate) - tabPath = os.path.join(path, 'tabges.htm') + tabPath = os.path.join(path, "tabges.htm") if not os.path.exists(tabPath): tabPath = None return (ergCandidate, tabPath) - + def findCandidates(self, path: str): - candidatesErg = self.__findRecursivelyCandidates(path, 'erg.htm') + candidatesErg = self.__findRecursivelyCandidates(path, "erg.htm") candidates = [self.__fingMatchingTabs(x) for x in candidatesErg] return candidates - - def findPreviewRoundCandidates(self, path: str): - candidates = self.__findRecursivelyCandidates(path, 'tabges.htm') - return candidates diff --git a/src/solo_turnier/html_parser.py b/src/solo_turnier/html_parser.py index 45b3f32..53077a7 100644 --- a/src/solo_turnier/html_parser.py +++ b/src/solo_turnier/html_parser.py @@ -3,96 +3,99 @@ from bs4 import BeautifulSoup import logging import re -from .types import HtmlPreviewParticipant, HtmlParticipant, HtmlResultTotalTable -from .types import HtmlPreviewImport as HtmlImport, HtmlResultImport +from .types import HtmlParticipant, HtmlResultTotalTable +from .types import HtmlResultImport from .group import GroupParser from .competition_class import CompetitionClassParser +import solo_turnier + class IncompleteRoundException(Exception): def __init__(self, *args): super(IncompleteRoundException, self).__init__(*args) -class HtmlParser: +class HtmlParser: def __init__(self, text: str, fileName: str = None): - self.l = logging.getLogger('solo_turnier.html_parser') - self.soup = BeautifulSoup(text, 'html.parser') + self.l = logging.getLogger("solo_turnier.html_parser") + self.soup = BeautifulSoup(text, "html.parser") self.fileName = fileName self.groupParser = GroupParser() self.classParser = CompetitionClassParser() def __repr__(self): if self.fileName is None: - return 'HtmlParser(direct text)' + return "HtmlParser(direct text)" else: - return f'HtmlParser({self.fileName})' - - def getEventTitle(self): - return self.soup.find('div', class_='eventhead').table.tr.td.contents[0] + return f"HtmlParser({self.fileName})" - def guessDataFromHtmlTitle(self, title = None): + def getEventTitle(self): + return self.soup.find("div", class_="eventhead").table.tr.td.contents[0] + + def guessDataFromHtmlTitle(self, title=None): if title is None: title = self.getEventTitle() - - match = re.compile('.*?ETW, Solos (.*)').match(title) + + match = re.compile('.*?ETW, Solos (.*?)(?: ".*")?').fullmatch(title) if match is None: + self.l.error('Cannot parse html title "%s". Possible bug?', title) raise Exception(f'Cannot parse title "{title}"') - + rest = match.group(1) - rawGroup, rawClass, dance = rest.split(' ', 2) + rawGroup, rawClass, dance = rest.split(" ", 2) return { - 'dance': dance.strip(), - 'class_': str(self.classParser.parseClass(rawClass, True)), - 'group': str(self.groupParser.parseClass(rawGroup)) + "dance": dance.strip(), + "class_": self.classParser.parseClass(rawClass, True), + "group": self.groupParser.parseGroup(rawGroup), } - def parseResult(self): + def parseResult(self) -> HtmlResultImport: participants = {} def __parseRows(rows, finalist: bool): def __parseRow(row): - tds = row.find_all('td') + tds = row.find_all("td") if len(tds) != 2: return - if tds[1].contents[0].startswith('Alle Starter weiter genommen.'): - self.l.info('No excluded starters found.') + if tds[1].contents[0].startswith("Alle Starter weiter genommen."): + self.l.info("No excluded starters found.") return - regex = re.compile('(.*) \\(([0-9]+)\\)') - + regex = re.compile("(.*) \\(([0-9]+)\\)") + place = tds[0].contents[0] - + match = regex.fullmatch(tds[1].contents[0]) if match is None: - self.l.error('Could not match %s to regex search pattern', str(tds)) - raise Exception(f'Could not match {tds} to regex search pattern') + self.l.error("Could not match %s to regex search pattern", str(tds)) + raise Exception(f"Could not match {tds} to regex search pattern") name = match.group(1) number = match.group(2) participant = HtmlParticipant(name, number) participant.finalist = finalist participants[participant] = place - + for row in rows: __parseRow(row) def __parseFirstTable(table): roundName = table.tr.td.contents[0] - if roundName != 'Endrunde': - self.l.warning('Found table with round name %s.', roundName) - raise IncompleteRoundException('Could not parse HTML file') - - __parseRows(table.find_all('tr')[2:], True) + if roundName != "Endrunde": + self.l.warning("Found table with round name %s.", roundName) + raise IncompleteRoundException("Could not parse HTML file") + + __parseRows(table.find_all("tr")[2:], True) def __parseRemainingTables(tables): for table in tables: - __parseRows(table.find_all('tr'), False) + __parseRows(table.find_all("tr"), False) + + tables = self.soup.find("div", class_="extract").find_all("table") - tables = self.soup.find('div', class_='extract').find_all('table') - try: if len(tables) > 0: __parseFirstTable(tables[0]) @@ -103,167 +106,116 @@ class HtmlParser: # title = self.soup.find('div', class_='eventhead').table.tr.td.contents[0] - # ret = HtmlImport(title, participants) - ret = HtmlResultImport(participants) + ret = HtmlResultImport(participants) return ret - def parsePreparationRound(self): - title = self.soup.find('div', class_='eventhead').table.tr.td.contents[0] - tableData = [] - rowTitles = [] - - def __mapBr(td): - for br in td.find_all('br'): - br.replace_with('\n') - td.smooth() - return td - - def __extractTitles(table): - for row in table.find_all('tr')[1:]: - rowTitles.append(__mapBr(row.td).string) - - def __extractColumns(table): - content = [] - - def __extractContent(td): - for br in td.find_all('br'): - br.replace_with('\n') - - span = td.span - if span is not None: - span = span.extract() - meta = span.string - else: - meta = None - - td.smooth() - - return { - 'text': td.string.replace('\xa0', ' ').strip(), - 'meta': meta - } - - def __extractRow(row): - entries = [] - for entry in row.find_all('td')[1:]: - entries.append(__extractContent(entry)) - return entries - - for row in table.find_all('tr')[1:]: - content.append(__extractRow(row)) - - return content - - def __mergeColumns(columns1, columns2): - return list(map(lambda x, y: x + y, columns1, columns2)) - - extract = self.soup.find('div', class_='extract') - tables = extract.find_all('table', class_='tab1') - - __extractTitles(tables[0]) - tableData = __extractColumns(tables[0]) - - for table in tables[1:]: - tableData = __mergeColumns(tableData, __extractColumns(table)) - - data = { - 'titles': rowTitles, - 'table': tableData - } - - return {'title': title, 'data': data} - - def cleanPreparationRoundImport(self, data): - def __cleanTable(table): - def __cleanText(s: str): - # print("cleaning string ", s) - return s.strip(' \n\xa0') - - def __cleanEntry(entry): - entry['text'] = __cleanText(entry['text']) - if entry['meta'] is not None: - entry['meta'] = __cleanText(entry['meta']) - - for row in table: - for entry in row: - # print(entry) - __cleanEntry(entry) - - data['title'] = data['title'].strip() - __cleanTable(data['data']['table']) - def parseIndividualResult(self, competitionGroup, competitionClass, dance): - participants = {} + rePlaceParser = re.compile("([0-9]+)(?:-([0-9]+))?") + + groupParser = solo_turnier.group.GroupParser() + classParser = solo_turnier.competition_class.CompetitionClassParser() + def __parseTable(table): - rows = table.find_all('tr') + rows = table.find_all("tr") def __getIds(): row = rows[1] - entries = row('td') + entries = row("td") entries = entries[1:] entries = [x for x in entries if len(x.contents[0].strip()) > 0] return [x.contents[0].strip() for x in entries] - + ids = __getIds() numIds = len(ids) - self.l.log(5, 'Found ids in dataset: %s', ids) + self.l.log(5, "Found ids in dataset: %s", ids) def findRowIndex(prefixStr): def isRowMatchingCriteria(row): if row.td.contents[0].startswith(prefixStr): return True return False + l = list(map(isRowMatchingCriteria, rows)) if True not in l: return None return l.index(True) - + def getPlaces(): - placeRowIdx = findRowIndex('Platz von') - placeTags = rows[placeRowIdx]('td')[1:(numIds+1)] + placeRowIdx = findRowIndex("Platz von") + placeTags = rows[placeRowIdx]("td")[1 : (numIds + 1)] + def getSinglePlaceStr(tag): - for br in tag('br'): - br.replace_with('-') + for br in tag("br"): + br.replace_with("-") tag.smooth() rawStr = tag.contents[0].strip() - if rawStr.endswith('-'): + if rawStr.endswith("-"): rawStr = rawStr[:-1] - return rawStr + + matcher = rePlaceParser.fullmatch(rawStr) + if matcher is None: + self.l.error( + "Could not parse place string '%s' to get fixture.", rawStr + ) + return None + + place = int(matcher.group(1)) + placeTo = matcher.group(2) + if placeTo is not None: + placeTo = int(placeTo) + + return solo_turnier.types.Place(place, placeTo) + places = list(map(getSinglePlaceStr, placeTags)) return places + places = getPlaces() - self.l.log(5, 'Found places: %s', places) + self.l.log(5, "Found places: %s", places) def getClass(): - classRow = findRowIndex('Startklasse') + classRow = findRowIndex("Startklasse") if classRow is not None: - classTags = rows[classRow]('td')[1:(numIds+1)] - return list(map(lambda x: x.contents[0], classTags)) + classTags = rows[classRow]("td")[1 : (numIds + 1)] + return list( + map( + lambda x: classParser.parseAbbreviatedClass(x.contents[0]), + classTags, + ) + ) return None + classes = getClass() - self.l.log(5, 'Classes: %s', classes) + self.l.log(5, "Classes: %s", classes) def getGroups(): - groupRow = findRowIndex('Startgruppe') + groupRow = findRowIndex("Startgruppe") if groupRow is not None: - classTags = rows[groupRow]('td')[1:(numIds+1)] - return list(map(lambda x: x.contents[0], classTags)) + groupTags = rows[groupRow]("td")[1 : (numIds + 1)] + return list( + map(lambda x: groupParser.parseGroup(x.contents[0]), groupTags) + ) return None + groups = getGroups() - self.l.log(5, 'Groups: %s', groups) + self.l.log(5, "Groups: %s", groups) for idx, id in enumerate(ids): cls = classes[idx] if classes is not None else None grp = groups[idx] if groups is not None else None - tup = (competitionGroup, competitionClass, dance, id) - participants[tup] = (places[idx], cls, grp) + tup = solo_turnier.types.CompetitionTuple( + competitionGroup, competitionClass, dance, int(id) + ) + fixture = solo_turnier.types.HtmlSingleCompetitionFixture( + place=places[idx], class_=cls, group=grp + ) + participants[tup] = fixture - tables = self.soup.find('div', class_='extract').find_all('table') + tables = self.soup.find("div", class_="extract").find_all("table") for table in tables: __parseTable(table) - - return HtmlResultTotalTable( participants) + + return HtmlResultTotalTable(participants) diff --git a/src/solo_turnier/output.py b/src/solo_turnier/output.py index 093ab7b..8be659c 100644 --- a/src/solo_turnier/output.py +++ b/src/solo_turnier/output.py @@ -1,4 +1,3 @@ - import logging from tabulate import tabulate import pprint @@ -6,17 +5,18 @@ import pprint import solo_turnier from solo_turnier import types -sections = ('Kin.', 'Jun.', 'Jug.', 'Sonst') +sections = ("Kin.", "Jun.", "Jug.", "Sonst") sectionMap = { - 'Kin.': 'Kinder', - 'Jun.': 'Junioren', - 'Jug.': 'Jugend', - 'Sonst': 'Undefiniert' + "Kin.": "Kinder", + "Jun.": "Junioren", + "Jug.": "Jugend", + "Sonst": "Undefiniert", } + class AbstractOutputter: def __init__(self): - self.worker = solo_turnier.worker.DataWorker() + self.worker = solo_turnier.workers.DataWorker.DataWorker() self.groups = [] self.dances = [] self.showIds = False @@ -24,24 +24,26 @@ class AbstractOutputter: def getRowData(self, person: solo_turnier.worker.ResultPerson, results): mappedResults = self.worker.mapPersonResultsToDanceList(results, self.dances) if self.showIds: - name = f'{person.name} ({person.id})' + name = f"{person.name} ({person.id})" else: name = person.name ret = [name] for result in mappedResults: if result is None: - ret.append('') + ret.append("") elif result.finalist == False: - ret.append('x') + ret.append("x") elif result.place == result.placeTo: - ret.append(f'{result.place}. ({result.class_})') + ret.append(f"{result.place}. ({result.class_})") else: - ret.append(f'{result.place}.-{result.placeTo}. ({result.class_})') + ret.append(f"{result.place}.-{result.placeTo}. ({result.class_})") return ret def getTabularData(self, data, section): - sortedPersons, self.showIds = self.worker.sortPersonsInGroup(self.groups[section]) + sortedPersons, self.showIds = self.worker.sortPersonsInGroup( + self.groups[section] + ) tableData = [] for person in sortedPersons: @@ -49,65 +51,95 @@ class AbstractOutputter: return tableData + class ConsoleOutputter(AbstractOutputter): def __init__(self): super().__init__() - self.l = logging.getLogger('solo_turnier.output.console') - + self.l = logging.getLogger("solo_turnier.output.console") + def __outputSection(self, data, section): tableData = self.getTabularData(data, section) - tableData = [['Name'] + self.dances] + tableData + tableData = [["Name"] + self.dances] + tableData print(f"Einzeltanzwettbewerb der {sectionMap[section]}") - print(tabulate(tableData, headers='firstrow', tablefmt='fancy_grid')) + print(tabulate(tableData, headers="firstrow", tablefmt="fancy_grid")) print() - def _outputGroup(self, group: solo_turnier.group.Group, groupResults: types.TotalGroupResult): - print(f"Einzeltanzwettbewerb der Gruppe {group}") - - tableData = [['Tanz'] + groupResults.dances] - participants = list(groupResults.results.keys()) + def _reshapeRow( + self, + results: list[solo_turnier.types.SingleParticipantResult], + dances: list[str], + ) -> list[solo_turnier.types.SingleParticipantResult]: + ret = [None for x in dances] + + for result in results: + if result.dance not in dances: + self.l.error( + "Result in unknown dance found in table. This is a bug. (%s)", + result, + ) + continue + + idx = dances.index(result.dance) + ret[idx] = result + + return ret + + def _outputGroup( + self, + group: solo_turnier.group.Group, + groupResults: solo_turnier.types.GroupTableData, + ): + if group is not None: + print(f"Einzeltanzwettbewerb der Gruppe {group}") + else: + print("Einzeltanzwettbewerbe ohne eindeutige Gruppenzuordnung") + + tableData = [["Tanz"] + groupResults.dances] + participants = list(groupResults.resultsInGroup.keys()) participants.sort(key=lambda x: (x.id, x.name)) for participant in participants: - results = groupResults.results[participant] + results = groupResults.resultsInGroup[participant] + + self.l.log(5, "Results of %s: %s", participant, results) + def mapResultColumn(result: types.SingleParticipantResult): def getPlace(place, placeTo): if placeTo is None: - return f'{place}.' + return f"{place}." else: - return f'{place}.-{placeTo}.' - + return f"{place}.-{placeTo}." + if result is None: - return '' - - placeNative = getPlace(result.placeNative, result.placeNativeTo) - place = getPlace(result.place, result.placeTo) - lineOne = f'{placeNative} ({result.nativeClass})' - lineTwo = f'[{place} in {result.competitionClass}]' + return "" + + placeNative = str(result.nativePlace) + place = str(result.place) + lineOne = f"{placeNative} ({result.nativeClass})" + lineTwo = f"[{place} in {result.competitionClass}]" lines = [lineOne, lineTwo] if not result.finalist: - lines = ['kein/e Finalist/in'] + lines - - return '\n'.join(lines) + lines = ["kein/e Finalist/in"] + lines + + return "\n".join(lines) mappedResults = map(mapResultColumn, results) - tableRow = [f'{participant.name} ({participant.id})'] + list(mappedResults) + tableRow = [f"{participant.name} ({participant.id})"] + list(mappedResults) tableData.append(tableRow) - - self.l.log(5, 'table data: %s', pprint.pformat(tableData)) - print(tabulate(tableData, headers='firstrow', tablefmt='fancy_grid')) - - def output(self, data: types.State4): - for idx, group in enumerate(data.groups): + self.l.log(5, "table data: %s", pprint.pformat(tableData)) + print(tabulate(tableData, headers="firstrow", tablefmt="fancy_grid")) + + def output(self, data: types.Stage5): + for idx, group in enumerate(data.resultsPerGroup): if idx > 0: print() - self.l.debug('Output for group %s', group) + self.l.debug("Output for group %s", group) - self._outputGroup(group, data.results[group]) + self._outputGroup(group, data.resultsPerGroup[group]) # self.groups = self.worker.collectPersonsInGroups(data) # self.dances = self.worker.getAllDancesInCompetitions(data) diff --git a/src/solo_turnier/participant.py b/src/solo_turnier/participant.py index 26c8b94..578f1bb 100644 --- a/src/solo_turnier/participant.py +++ b/src/solo_turnier/participant.py @@ -1,34 +1,28 @@ - class Person: - def __init__( - self, - firstName: str, - lastName: str, - club: str, - group: str - ): + def __init__(self, firstName: str, lastName: str, club: str, group: str): self.firstName = firstName self.lastName = lastName self.club = club self.group = group - + def __eq__(self, o): if not isinstance(o, Person): False - + return ( - self.firstName == o.firstName and - self.lastName == o.lastName and - self.club == o.club and - self.group == o.group + self.firstName == o.firstName + and self.lastName == o.lastName + and self.club == o.club + and self.group == o.group ) - + def getTuple(self): return (self.firstName, self.lastName, self.club) - - def __repr__(self): - return f'{self.firstName} {self.lastName} ({self.club}, {self.group})' - - def __hash__(self): - return self.firstName.__hash__() + self.lastName.__hash__() + self.club.__hash__() + def __repr__(self): + return f"{self.firstName} {self.lastName} ({self.club}, {self.group})" + + def __hash__(self): + return ( + self.firstName.__hash__() + self.lastName.__hash__() + self.club.__hash__() + ) diff --git a/src/solo_turnier/reader.py b/src/solo_turnier/reader.py deleted file mode 100644 index d139a22..0000000 --- a/src/solo_turnier/reader.py +++ /dev/null @@ -1,101 +0,0 @@ - -import solo_turnier -import csv -import os -import logging -import re -from pprint import pformat -from .types import CSVResultRow as ResultRow - -class CSVResultReader: - def __init__(self, fileName: str): - self.fileName = fileName - self.l = logging.getLogger('solo_turnier.reader.CSVResultReader') - - def readFile(self): - with open(self.fileName, 'r') as fp: - dialect = csv.Sniffer().sniff(fp.read(1024)) - fp.seek(0) - - csvReader = csv.reader(fp, dialect) - - rows = [] - for row in csvReader: - rows.append(row) - - ret = { - 'header': rows[0], - 'data': rows[1:] - } - - self.l.log(5, 'Imported results from allresults.csv file: %s', (ret)) - return ret - - def extractResult(self, entries = None) -> list[ResultRow]: - if entries is None: - entries = self.readFile() - - groupParser = solo_turnier.group.GroupParser() - classParser = solo_turnier.competition_class.CompetitionClassParser() - - def __processRow(row): - result = ResultRow( - competitionGroup=groupParser.parseClass(row[2]), - competitionClass=classParser.parseClass(row[3]), - dance=row[4], - id=row[5], - firstName=row[6], lastName=row[7], - club=row[10], - place=row[12], placeTo=row[13], - group=groupParser.parseClass(row[15]), - class_=classParser.parseClass(row[16]) - ) - self.l.log(5, 'Found row in CSV: %s', result) - return result - - ret = list(map(__processRow, entries['data'])) - - self.l.log(5, 'Extracted rows from CSV data: %s', ret) - return ret - -class CSVExtractor: - def __init__(self): - self.l = logging.getLogger('solo_turnier.worker') - self.__groupMaps = { - 'Kinder': 'Kin.', - 'Junioren': 'Jun.', - 'Jugend': 'Jug.' - } - self.__classMaps = { - 'Newcomer': 'Newc.', - 'Beginner': 'Beg.', - 'Advanced': 'Adv.' - } - - def __mapGroup(self, group): - return self.__groupMaps.get(group, group) - - def __mapClass(self, class_): - return self.__classMaps.get(class_, class_) - - def mapCSVImport(self, imported) -> list[ResultRow]: - ret = [] - - def __processRow(row): - result = ResultRow( - competitionGroup=self.__mapGroup(row[2]), - competitionClass=self.__mapClass(row[3]), - dance=row[4], - id=row[5], - firstName=row[6], lastName=row[7], - club=row[10], - place=row[12], placeTo=row[13], - group=self.__mapGroup(row[15]), class_=self.__mapClass(row[16]) - ) - ret.append(result) - self.l.log(5, 'Found row in CSV: %s', result) - - for row in imported['data']: - __processRow(row) - - return ret diff --git a/src/solo_turnier/static/style.css b/src/solo_turnier/static/style.css index 5158548..1320070 100644 --- a/src/solo_turnier/static/style.css +++ b/src/solo_turnier/static/style.css @@ -22,6 +22,12 @@ color: gray; } +.tab-summary .no-finalist-dance { + color: gray; + text-decoration-style: solid; + text-decoration-line: line-through; +} + @media print { @page { size: landscape; diff --git a/src/solo_turnier/templates/index.html b/src/solo_turnier/templates/index.html index 67f77bb..6927f0c 100644 --- a/src/solo_turnier/templates/index.html +++ b/src/solo_turnier/templates/index.html @@ -10,18 +10,22 @@
{#Teilnehmer | - {% for dance in data.results[group].dances %} + {% for dance in data.resultsPerGroup[group].dances %}{{ dance }} | {% endfor %}
---|---|
{{ participant.name }} ({{ participant.id }}) | - {% for dance in data.results[group].dances %} + {% for dance in data.resultsPerGroup[group].dances %} {% block danceResult scoped %} {% set res = activeGroup[participant][loop.index0] %}
{% if res is not none %}
{% if not participant.finalist %}
- Kein/e Finalist/in
+ Kein/e Finalist/in {% endif %} - {{ res.getNativePlace() }} ({{ res.nativeClass }}) + + {{ res.getNativePlace() }} ({{ res.nativeClass }}) + - {{ res.getPlace() }} in {{ res.competitionClass }} + {{ res.place }} in {{ res.competitionClass }} {% endif %} |
diff --git a/src/solo_turnier/tests/test_competition_class.py b/src/solo_turnier/tests/test_competition_class.py
index 88287ef..2feb016 100644
--- a/src/solo_turnier/tests/test_competition_class.py
+++ b/src/solo_turnier/tests/test_competition_class.py
@@ -1,21 +1,23 @@
import solo_turnier.competition_class
import pytest
+
@pytest.fixture(params=range(9))
def fix_pureClass(request):
cases = (
- ('Newc', 'Newc.'),
- ('Newc.', 'Newc.'),
- ('Newcomer', 'Newc.'),
- ('Beg', 'Beg.'),
- ('Beg.', 'Beg.'),
- ('Beginner', 'Beg.'),
- ('Adv', 'Adv.'),
- ('Adv.', 'Adv.'),
- ('Advanced', 'Adv.'),
+ ("Newc", "Newc."),
+ ("Newc.", "Newc."),
+ ("Newcomer", "Newc."),
+ ("Beg", "Beg."),
+ ("Beg.", "Beg."),
+ ("Beginner", "Beg."),
+ ("Adv", "Adv."),
+ ("Adv.", "Adv."),
+ ("Advanced", "Adv."),
)
return cases[request.param]
+
def test_pureClassParsing(fix_pureClass):
className = fix_pureClass[0]
expected = fix_pureClass[1]
@@ -28,39 +30,43 @@ def test_pureClassParsing(fix_pureClass):
assert parser.isPureClass(className)
+
def test_classParsingWithPreview():
parser = solo_turnier.competition_class.CompetitionClassParser()
- ret = parser.parseClass('Sichtung', True)
+ ret = parser.parseClass("Sichtung", True)
assert isinstance(ret, solo_turnier.competition_class.CompetitionClass)
- assert str(ret) == 'Sichtung'
+ assert str(ret) == "Sichtung"
+
+ assert parser.isPureClass("Sichtung", True)
- assert parser.isPureClass('Sichtung', True)
def test_classParsingInvalidPreview():
parser = solo_turnier.competition_class.CompetitionClassParser()
try:
- parser.parseClass('Sichtung')
+ parser.parseClass("Sichtung")
assert False
except:
assert True
try:
- parser.isPureClass('Sichtung')
+ parser.isPureClass("Sichtung")
assert False
except:
assert True
+
@pytest.fixture(params=range(4))
def fix_combinedClass(request):
cases = (
- ('Newc/Beg', 'Newc./Beg.'),
- ('Newc./Beg', 'Newc./Beg.'),
- ('Beginner/Adv', 'Beg./Adv.'),
- ('Beg/Adv', 'Beg./Adv.'),
+ ("Newc/Beg", "Newc./Beg."),
+ ("Newc./Beg", "Newc./Beg."),
+ ("Beginner/Adv", "Beg./Adv."),
+ ("Beg/Adv", "Beg./Adv."),
)
return cases[request.param]
+
def test_combinedClassParsing(fix_combinedClass):
className = fix_combinedClass[0]
expected = fix_combinedClass[1]
diff --git a/src/solo_turnier/tests/test_csvReader.py b/src/solo_turnier/tests/test_csvReader.py
index 80436a0..3b6cd87 100644
--- a/src/solo_turnier/tests/test_csvReader.py
+++ b/src/solo_turnier/tests/test_csvReader.py
@@ -2,12 +2,13 @@ import solo_turnier.reader
import os
import json
+
def test_import():
- fileName = os.path.join(os.path.dirname(__file__), 'reader', 'test.csv')
+ fileName = os.path.join(os.path.dirname(__file__), "reader", "test.csv")
reader = solo_turnier.reader.CSVResultReader(fileName)
ret = reader.readFile()
- with open(os.path.join(os.path.dirname(__file__), 'reader', 'expected.json')) as fp:
+ with open(os.path.join(os.path.dirname(__file__), "reader", "expected.json")) as fp:
expected = json.load(fp)
assert ret == expected
diff --git a/src/solo_turnier/tests/test_group.py b/src/solo_turnier/tests/test_group.py
index 09eb5a8..d0e5e47 100644
--- a/src/solo_turnier/tests/test_group.py
+++ b/src/solo_turnier/tests/test_group.py
@@ -1,21 +1,23 @@
import solo_turnier.group
import pytest
+
@pytest.fixture(params=range(9))
def fix_pureClass(request):
cases = (
- ('Kin', 'Kin.'),
- ('Kin.', 'Kin.'),
- ('Kinder', 'Kin.'),
- ('Jun', 'Jun.'),
- ('Jun.', 'Jun.'),
- ('Junioren', 'Jun.'),
- ('Jug', 'Jug.'),
- ('Jug.', 'Jug.'),
- ('Jugend', 'Jug.'),
+ ("Kin", "Kin."),
+ ("Kin.", "Kin."),
+ ("Kinder", "Kin."),
+ ("Jun", "Jun."),
+ ("Jun.", "Jun."),
+ ("Junioren", "Jun."),
+ ("Jug", "Jug."),
+ ("Jug.", "Jug."),
+ ("Jugend", "Jug."),
)
return cases[request.param]
+
def test_pureClassParsing(fix_pureClass):
className = fix_pureClass[0]
expected = fix_pureClass[1]
@@ -28,16 +30,18 @@ def test_pureClassParsing(fix_pureClass):
assert parser.isPureClass(className)
+
@pytest.fixture(params=range(4))
def fix_combinedClass(request):
cases = (
- ('Kin/Jun', 'Kin./Jun.'),
- ('Kin./Jun', 'Kin./Jun.'),
- ('Junioren/Jug', 'Jun./Jug.'),
- ('Jun/Jug', 'Jun./Jug.'),
+ ("Kin/Jun", "Kin./Jun."),
+ ("Kin./Jun", "Kin./Jun."),
+ ("Junioren/Jug", "Jun./Jug."),
+ ("Jun/Jug", "Jun./Jug."),
)
return cases[request.param]
+
def test_combinedClassParsing(fix_combinedClass):
className = fix_combinedClass[0]
expected = fix_combinedClass[1]
diff --git a/src/solo_turnier/tests/test_html_locator.py b/src/solo_turnier/tests/test_html_locator.py
index a24a514..757edd6 100644
--- a/src/solo_turnier/tests/test_html_locator.py
+++ b/src/solo_turnier/tests/test_html_locator.py
@@ -1,17 +1,17 @@
-
import os
import solo_turnier.html_locator
+
def test_fetchLocationCandidates():
- folder = os.path.join(os.path.dirname(__file__), 'html_locator', 'export')
+ folder = os.path.join(os.path.dirname(__file__), "html_locator", "export")
relFolder = os.path.relpath(folder)
locator = solo_turnier.html_locator.HtmlLocator()
candidates = locator.findCandidates(relFolder)
expected = [
- 'solo_turnier/tests/html_locator/export/2-bar/erg.htm',
- 'solo_turnier/tests/html_locator/export/3-baz/erg.htm',
- 'solo_turnier/tests/html_locator/export/3-baz/subfolder/4-baz/erg.htm'
+ "solo_turnier/tests/html_locator/export/2-bar/erg.htm",
+ "solo_turnier/tests/html_locator/export/3-baz/erg.htm",
+ "solo_turnier/tests/html_locator/export/3-baz/subfolder/4-baz/erg.htm",
]
assert set(candidates) == set(expected)
diff --git a/src/solo_turnier/tests/test_html_parser.py b/src/solo_turnier/tests/test_html_parser.py
index d5ecd66..dbccb44 100644
--- a/src/solo_turnier/tests/test_html_parser.py
+++ b/src/solo_turnier/tests/test_html_parser.py
@@ -4,20 +4,22 @@ import json
import solo_turnier.html_parser
-@pytest.fixture(scope='module', params=range(2))
+
+@pytest.fixture(scope="module", params=range(2))
def dataProviderHtmlParser(request):
- variant = str(request.param+1)
- dir = os.path.join(os.path.dirname(__file__), 'html_parser', 'erg', variant)
- htmlFile = os.path.join(dir, 'erg.htm')
- jsonFile = os.path.join(dir, 'expected.json')
-
- with open(htmlFile, 'r') as fp:
+ variant = str(request.param + 1)
+ dir = os.path.join(os.path.dirname(__file__), "html_parser", "erg", variant)
+ htmlFile = os.path.join(dir, "erg.htm")
+ jsonFile = os.path.join(dir, "expected.json")
+
+ with open(htmlFile, "r") as fp:
html = fp.read()
- with open(jsonFile, 'r') as fp:
+ with open(jsonFile, "r") as fp:
jsonContent = json.load(fp)
-
+
return (html, jsonContent)
+
def test_extractDataFromHtml(dataProviderHtmlParser):
htmlString = dataProviderHtmlParser[0]
expected = dataProviderHtmlParser[1]
@@ -29,67 +31,71 @@ def test_extractDataFromHtml(dataProviderHtmlParser):
for i in actualResult.participants:
participants[i] = actualResult.participants[i].__dict__
- assert actualResult.title == expected['title']
- assert participants == expected['participants']
+ assert actualResult.title == expected["title"]
+ assert participants == expected["participants"]
+
@pytest.fixture(params=range(6))
def fixture_guessDataFromTitle(request):
cases = {
- '09.07.2022 - ETW, Solos Jun. Beginner Jive': {
- 'class_': 'Beg.',
- 'dance': 'Jive',
- 'group': 'Jun.'
+ "09.07.2022 - ETW, Solos Jun. Beginner Jive": {
+ "class_": "Beg.",
+ "dance": "Jive",
+ "group": "Jun.",
},
- '09.07.2022 - ETW, Solos Jun. Newc./Beg. Rumba': {
- 'class_': 'Newc./Beg.',
- 'dance': 'Rumba',
- 'group': 'Jun.'
+ "09.07.2022 - ETW, Solos Jun. Newc./Beg. Rumba": {
+ "class_": "Newc./Beg.",
+ "dance": "Rumba",
+ "group": "Jun.",
},
- '09.07.2022 - ETW, Solos Kin./Jun. Beginner Cha Cha': {
- 'class_': 'Beg.',
- 'dance': 'Cha Cha',
- 'group': 'Kin./Jun.'
+ "09.07.2022 - ETW, Solos Kin./Jun. Beginner Cha Cha": {
+ "class_": "Beg.",
+ "dance": "Cha Cha",
+ "group": "Kin./Jun.",
},
- '09.07.2022 - ETW, Solos Kin. Newcomer Samba': {
- 'class_': 'Newc.',
- 'dance': 'Samba',
- 'group': 'Kin.'
+ "09.07.2022 - ETW, Solos Kin. Newcomer Samba": {
+ "class_": "Newc.",
+ "dance": "Samba",
+ "group": "Kin.",
},
- '09.07.2022 - ETW, Solos Jugend Beg./Adv. Wiener Walzer': {
- 'class_': 'Beg./Adv.',
- 'dance': 'Wiener Walzer',
- 'group': 'Jug.'
+ "09.07.2022 - ETW, Solos Jugend Beg./Adv. Wiener Walzer": {
+ "class_": "Beg./Adv.",
+ "dance": "Wiener Walzer",
+ "group": "Jug.",
},
- '09.07.2022 - ETW, Solos Jugend Sichtung Wiener Walzer': {
- 'class_': 'Sichtung',
- 'dance': 'Wiener Walzer',
- 'group': 'Jug.'
+ "09.07.2022 - ETW, Solos Jugend Sichtung Wiener Walzer": {
+ "class_": "Sichtung",
+ "dance": "Wiener Walzer",
+ "group": "Jug.",
},
}
keys = list(cases.keys())
key = keys[request.param]
return (key, cases[key])
+
def test_guessDataFromTitle(fixture_guessDataFromTitle):
- parser = solo_turnier.html_parser.HtmlParser('')
+ parser = solo_turnier.html_parser.HtmlParser("")
ret = parser.guessDataFromHtmlTitle(fixture_guessDataFromTitle[0])
assert ret == fixture_guessDataFromTitle[1]
+
@pytest.fixture(params=range(1))
def fixture_parsePreparationResult(request):
- variant = str(request.param+1)
- dir = os.path.join(os.path.dirname(__file__), 'html_parser', 'tabges', variant)
- htmlFile = os.path.join(dir, 'tabges.htm')
- jsonFile = os.path.join(dir, 'expected.json')
-
- with open(htmlFile, 'r') as fp:
+ variant = str(request.param + 1)
+ dir = os.path.join(os.path.dirname(__file__), "html_parser", "tabges", variant)
+ htmlFile = os.path.join(dir, "tabges.htm")
+ jsonFile = os.path.join(dir, "expected.json")
+
+ with open(htmlFile, "r") as fp:
html = fp.read()
- with open(jsonFile, 'r') as fp:
+ with open(jsonFile, "r") as fp:
jsonContent = json.load(fp)
-
+
return (html, jsonContent)
+
def test_parsePreparationResult(fixture_parsePreparationResult):
html = fixture_parsePreparationResult[0]
jsonContent = fixture_parsePreparationResult[1]
@@ -99,25 +105,27 @@ def test_parsePreparationResult(fixture_parsePreparationResult):
assert ret == jsonContent
+
@pytest.fixture(params=range(1))
def fixture_cleanPreparationImport(request):
- variant = str(request.param+1)
- dir = os.path.join(os.path.dirname(__file__), 'html_parser', 'tabges', variant)
- srcFile = os.path.join(dir, 'expected.json')
- expectedFile = os.path.join(dir, 'cleaned.json')
-
- with open(srcFile, 'r') as fp:
+ variant = str(request.param + 1)
+ dir = os.path.join(os.path.dirname(__file__), "html_parser", "tabges", variant)
+ srcFile = os.path.join(dir, "expected.json")
+ expectedFile = os.path.join(dir, "cleaned.json")
+
+ with open(srcFile, "r") as fp:
source = json.load(fp)
- with open(expectedFile, 'r') as fp:
+ with open(expectedFile, "r") as fp:
expected = json.load(fp)
-
+
return (source, expected)
+
def test_cleanPreparationImport(fixture_cleanPreparationImport):
src = fixture_cleanPreparationImport[0]
expected = fixture_cleanPreparationImport[1]
- parser = solo_turnier.html_parser.HtmlParser('')
+ parser = solo_turnier.html_parser.HtmlParser("")
parser.cleanPreparationRoundImport(src)
assert src == expected
diff --git a/src/solo_turnier/tests/test_types.py b/src/solo_turnier/tests/test_types.py
index ca6ffdc..939358d 100644
--- a/src/solo_turnier/tests/test_types.py
+++ b/src/solo_turnier/tests/test_types.py
@@ -1,10 +1,11 @@
import pytest
import solo_turnier.types as types
+
def test_HtmlPreviewParticipant_eq():
- name = 'Max Mustermann'
+ name = "Max Mustermann"
id = 123
- group = 'Kin'
+ group = "Kin"
participant = types.HtmlPreviewParticipant(name, id, group)
l = []
@@ -13,6 +14,6 @@ def test_HtmlPreviewParticipant_eq():
assert participant in l
assert types.HtmlPreviewParticipant(name, id, group) in l
- assert types.HtmlPreviewParticipant('Maxime Musterfrau', id, group) not in l
+ assert types.HtmlPreviewParticipant("Maxime Musterfrau", id, group) not in l
assert types.HtmlPreviewParticipant(name, 234, group) not in l
- assert types.HtmlPreviewParticipant(name, id, 'Jun') not in l
+ assert types.HtmlPreviewParticipant(name, id, "Jun") not in l
diff --git a/src/solo_turnier/tests/test_worker.py b/src/solo_turnier/tests/test_worker.py
index 5a8c18b..957b57c 100644
--- a/src/solo_turnier/tests/test_worker.py
+++ b/src/solo_turnier/tests/test_worker.py
@@ -4,178 +4,298 @@ import json
import pytest
import pytest_mock
+
def __importJSONData(name):
- path = os.path.join(os.path.dirname(__file__), 'worker', name)
- with open(path, 'r') as fp:
+ path = os.path.join(os.path.dirname(__file__), "worker", name)
+ with open(path, "r") as fp:
return json.load(fp)
+
@pytest.fixture
def fixture_csvExtractor():
- data = __importJSONData('csvImport.json')
- expected = __importJSONData('csvImportResult.json')
+ data = __importJSONData("csvImport.json")
+ expected = __importJSONData("csvImportResult.json")
return (data, expected)
+
def test_csvExtractor(fixture_csvExtractor):
extractor = worker.CSVExtractor()
mapped = extractor.mapCSVImport(fixture_csvExtractor[0])
assert len(mapped) == len(fixture_csvExtractor[1])
- for i,elem in enumerate(fixture_csvExtractor[1]):
+ for i, elem in enumerate(fixture_csvExtractor[1]):
assert mapped[i].__dict__ == elem
+
def test_extractPersonFromRow():
- row = worker.ResultRow('Max', 'Mustermann', 'TSC Entenhausen', '2', 'Kin', 'Adv.', 'Rumba', '2', '2', 'Kin.', 'Beg./Adv.')
+ row = worker.ResultRow(
+ "Max",
+ "Mustermann",
+ "TSC Entenhausen",
+ "2",
+ "Kin",
+ "Adv.",
+ "Rumba",
+ "2",
+ "2",
+ "Kin.",
+ "Beg./Adv.",
+ )
person = worker.ResultPerson.extractFromResultRow(row)
expected = {
- 'firstName': 'Max',
- 'lastName': 'Mustermann',
- 'name': 'Max Mustermann',
- 'club': 'TSC Entenhausen',
- 'id': None,
- 'group': None
+ "firstName": "Max",
+ "lastName": "Mustermann",
+ "name": "Max Mustermann",
+ "club": "TSC Entenhausen",
+ "id": None,
+ "group": None,
}
assert person.__dict__ == expected
+
def test_extractCompetitionFromRow():
- row = worker.ResultRow('Max', 'Mustermann', 'TSC Entenhausen', '2', 'Kin', 'Adv.', 'Rumba', '2', '2', 'Kin.', 'Beg./Adv.')
+ row = worker.ResultRow(
+ "Max",
+ "Mustermann",
+ "TSC Entenhausen",
+ "2",
+ "Kin",
+ "Adv.",
+ "Rumba",
+ "2",
+ "2",
+ "Kin.",
+ "Beg./Adv.",
+ )
competition = worker.CompetitionResult.extractFromResultRow(row)
expected = {
- 'dance': 'Rumba',
- 'class_': 'Adv.',
- 'group': 'Kin',
- 'place': '2',
- 'placeTo': '2',
- 'id': 2,
- 'finalist': None,
- 'competitionGroup': 'Kin.',
- 'competitionClass': 'Beg./Adv.'
+ "dance": "Rumba",
+ "class_": "Adv.",
+ "group": "Kin",
+ "place": "2",
+ "placeTo": "2",
+ "id": 2,
+ "finalist": None,
+ "competitionGroup": "Kin.",
+ "competitionClass": "Beg./Adv.",
}
assert competition.__dict__ == expected
+
def test_combineRowsByPerson():
rows = [
- worker.ResultRow('Max', 'Mustermann', 'TSC Entenhausen', '2', 'Kin', 'Adv.', 'Cha Cha', '-', '-', 'Kin.', 'Adv.'),
- worker.ResultRow('Max', 'Mustermann', 'TSC Entenhausen', '2', 'Kin', 'Adv.', 'Rumba', '2', '2', 'Kin.', 'Adv.'),
- worker.ResultRow('Max', 'Mustermann', 'TSC Entenhausen', '2', 'Kin', 'Beg.', 'Jive', '1', '1', 'Kin.', 'Beg.'),
- worker.ResultRow('Maxime', 'Musterfrau', '1. SC Entenhausen', '1', 'Kin', 'Adv.', 'Rumba', '1', '1', 'Kin.', 'Adv.')
+ worker.ResultRow(
+ "Max",
+ "Mustermann",
+ "TSC Entenhausen",
+ "2",
+ "Kin",
+ "Adv.",
+ "Cha Cha",
+ "-",
+ "-",
+ "Kin.",
+ "Adv.",
+ ),
+ worker.ResultRow(
+ "Max",
+ "Mustermann",
+ "TSC Entenhausen",
+ "2",
+ "Kin",
+ "Adv.",
+ "Rumba",
+ "2",
+ "2",
+ "Kin.",
+ "Adv.",
+ ),
+ worker.ResultRow(
+ "Max",
+ "Mustermann",
+ "TSC Entenhausen",
+ "2",
+ "Kin",
+ "Beg.",
+ "Jive",
+ "1",
+ "1",
+ "Kin.",
+ "Beg.",
+ ),
+ worker.ResultRow(
+ "Maxime",
+ "Musterfrau",
+ "1. SC Entenhausen",
+ "1",
+ "Kin",
+ "Adv.",
+ "Rumba",
+ "1",
+ "1",
+ "Kin.",
+ "Adv.",
+ ),
]
dataWorker = worker.DataWorker()
result = dataWorker.combineRowsByPerson(rows)
expected = {
- worker.ResultPerson('Max', 'Mustermann', 'TSC Entenhausen'): [
- worker.CompetitionResult('Rumba', 'Kin', 'Adv.', '2', '2', '2', 'Kin.', 'Adv.'),
- worker.CompetitionResult('Jive', 'Kin', 'Beg.', '1', '1', '2', 'Kin.', 'Beg.')
+ worker.ResultPerson("Max", "Mustermann", "TSC Entenhausen"): [
+ worker.CompetitionResult(
+ "Rumba", "Kin", "Adv.", "2", "2", "2", "Kin.", "Adv."
+ ),
+ worker.CompetitionResult(
+ "Jive", "Kin", "Beg.", "1", "1", "2", "Kin.", "Beg."
+ ),
+ ],
+ worker.ResultPerson("Maxime", "Musterfrau", "1. SC Entenhausen"): [
+ worker.CompetitionResult(
+ "Rumba", "Kin", "Adv.", "1", "1", "1", "Kin.", "Adv."
+ )
],
- worker.ResultPerson('Maxime', 'Musterfrau', '1. SC Entenhausen'): [
- worker.CompetitionResult('Rumba', 'Kin', 'Adv.', '1', '1', '1', 'Kin.', 'Adv.')
- ]
}
assert result == expected
+
def test_checkUniqueIds_True():
- person1 = worker.ResultPerson('Max', 'Mustermann', 'TSC Entenhausen')
- person2 = worker.ResultPerson('Maxime', 'Musterfrau', '1. SC Entenhausen')
+ person1 = worker.ResultPerson("Max", "Mustermann", "TSC Entenhausen")
+ person2 = worker.ResultPerson("Maxime", "Musterfrau", "1. SC Entenhausen")
data = {
person1: [
- worker.CompetitionResult('Rumba', 'Kin', 'Adv.', '2', '2', 2, 'Kin.', 'Adv.'),
- worker.CompetitionResult('Jive', 'Kin', 'Beg.', '1', '1', 2, 'Kin.', 'Beg.')
+ worker.CompetitionResult(
+ "Rumba", "Kin", "Adv.", "2", "2", 2, "Kin.", "Adv."
+ ),
+ worker.CompetitionResult(
+ "Jive", "Kin", "Beg.", "1", "1", 2, "Kin.", "Beg."
+ ),
],
person2: [
- worker.CompetitionResult('Rumba', 'Kin', 'Adv.', '1', '1', 1, 'Kin.', 'Adv.')
- ]
+ worker.CompetitionResult(
+ "Rumba", "Kin", "Adv.", "1", "1", 1, "Kin.", "Adv."
+ )
+ ],
}
dataWorker = worker.DataWorker()
assert dataWorker.checkUniqueIds(data) == True
assert person1.id == 2
assert person2.id == 1
+
def test_checkUniqueIds_False():
- person1 = worker.ResultPerson('Max', 'Mustermann', 'TSC Entenhausen')
- person2 = worker.ResultPerson('Maxime', 'Musterfrau', '1. SC Entenhausen')
+ person1 = worker.ResultPerson("Max", "Mustermann", "TSC Entenhausen")
+ person2 = worker.ResultPerson("Maxime", "Musterfrau", "1. SC Entenhausen")
data = {
person1: [
- worker.CompetitionResult('Rumba', 'Kin', 'Adv.', '2', '2', 2, 'Kin.', 'Adv.'),
- worker.CompetitionResult('Jive', 'Kin', 'Beg.', '1', '1', 3, 'Kin.', 'Beg.')
+ worker.CompetitionResult(
+ "Rumba", "Kin", "Adv.", "2", "2", 2, "Kin.", "Adv."
+ ),
+ worker.CompetitionResult(
+ "Jive", "Kin", "Beg.", "1", "1", 3, "Kin.", "Beg."
+ ),
],
person2: [
- worker.CompetitionResult('Rumba', 'Kin', 'Adv.', '1', '1', 1, 'Kin.', 'Adv.')
- ]
+ worker.CompetitionResult(
+ "Rumba", "Kin", "Adv.", "1", "1", 1, "Kin.", "Adv."
+ )
+ ],
}
dataWorker = worker.DataWorker()
assert dataWorker.checkUniqueIds(data) == False
assert person1.id == None
assert person2.id == 1
+
@pytest.fixture(params=range(5))
def fixture_consolidateGroups(request):
- person1 = worker.ResultPerson('Max 1', 'Mustermann', 'TSC Entenhausen')
- person2 = worker.ResultPerson('Max 2', 'Mustermann', 'TSC Entenhausen')
- person3 = worker.ResultPerson('Max 3', 'Mustermann', 'TSC Entenhausen')
- person4 = worker.ResultPerson('Max 4', 'Mustermann', 'TSC Entenhausen')
+ person1 = worker.ResultPerson("Max 1", "Mustermann", "TSC Entenhausen")
+ person2 = worker.ResultPerson("Max 2", "Mustermann", "TSC Entenhausen")
+ person3 = worker.ResultPerson("Max 3", "Mustermann", "TSC Entenhausen")
+ person4 = worker.ResultPerson("Max 4", "Mustermann", "TSC Entenhausen")
# persons = (person1, person2, person3, person4)
dict1 = {
person1: [
- worker.CompetitionResult('Rumba', 'Kin.', 'Adv.', '2', '2', 2, 'Kin.', 'Adv.'),
- worker.CompetitionResult('Jive', 'Kin.', 'Beg.', '1', '1', 3, 'Kin.', 'Beg.')
+ worker.CompetitionResult(
+ "Rumba", "Kin.", "Adv.", "2", "2", 2, "Kin.", "Adv."
+ ),
+ worker.CompetitionResult(
+ "Jive", "Kin.", "Beg.", "1", "1", 3, "Kin.", "Beg."
+ ),
]
}
dict2 = {
person2: [
- worker.CompetitionResult('Rumba', 'Kin.', 'Adv.', '2', '2', 2, 'Kin.', 'Adv.'),
- worker.CompetitionResult('Jive', 'Kin./Jun.', 'Beg.', '1', '1', 3, 'Kin./Jun.', 'Beg.')
+ worker.CompetitionResult(
+ "Rumba", "Kin.", "Adv.", "2", "2", 2, "Kin.", "Adv."
+ ),
+ worker.CompetitionResult(
+ "Jive", "Kin./Jun.", "Beg.", "1", "1", 3, "Kin./Jun.", "Beg."
+ ),
]
}
dict3 = {
person3: [
- worker.CompetitionResult('Rumba', 'Kin.', 'Adv.', '2', '2', 2, 'Kin.', 'Adv.')
+ worker.CompetitionResult(
+ "Rumba", "Kin.", "Adv.", "2", "2", 2, "Kin.", "Adv."
+ )
]
}
dict4 = {
person4: [
- worker.CompetitionResult('Rumba', 'Kin./Jun.', 'Adv.', '2', '2', 2, 'Kin./Jun.', 'Adv.')
+ worker.CompetitionResult(
+ "Rumba", "Kin./Jun.", "Adv.", "2", "2", 2, "Kin./Jun.", "Adv."
+ )
]
}
dict5 = {
person4: [
- worker.CompetitionResult('Rumba', 'Kin./Jun.', 'Adv.', '2', '2', 2, 'Kin./Jun.', 'Adv.'),
- worker.CompetitionResult('Cha Cha', 'Jun./Jug.', 'Beg.', '3', '4', 2, 'Jun./Jug.', 'Beg.')
+ worker.CompetitionResult(
+ "Rumba", "Kin./Jun.", "Adv.", "2", "2", 2, "Kin./Jun.", "Adv."
+ ),
+ worker.CompetitionResult(
+ "Cha Cha", "Jun./Jug.", "Beg.", "3", "4", 2, "Jun./Jug.", "Beg."
+ ),
]
}
cases = (
- (dict1|dict3, (True, False), {}),
- (dict1|dict2|dict3, (True, True), {}),
- (dict4, (False, False), {person4: 'Kin./Jun.'}),
- (dict1|dict2|dict3|dict4, (False, True), {person4: 'Kin./Jun.'}),
- (dict5, (True, True), {person4: 'Jun.'}),
+ (dict1 | dict3, (True, False), {}),
+ (dict1 | dict2 | dict3, (True, True), {}),
+ (dict4, (False, False), {person4: "Kin./Jun."}),
+ (dict1 | dict2 | dict3 | dict4, (False, True), {person4: "Kin./Jun."}),
+ (dict5, (True, True), {person4: "Jun."}),
)
return cases[request.param]
+
@pytest.fixture(params=range(2))
def fixture_consolidateGroups_fail(request, fixture_consolidateGroups):
- person = worker.ResultPerson('Max 5', 'Mustermann', 'TSC Entenhausen')
+ person = worker.ResultPerson("Max 5", "Mustermann", "TSC Entenhausen")
dict1 = {
person: [
- worker.CompetitionResult('Rumba', 'Kin.', 'Adv.', '2', '2', 2, 'Kin.', 'Adv.'),
- worker.CompetitionResult('Jive', 'Jun.', 'Beg.', '1', '1', 3, 'Jun.', 'Adv.')
+ worker.CompetitionResult(
+ "Rumba", "Kin.", "Adv.", "2", "2", 2, "Kin.", "Adv."
+ ),
+ worker.CompetitionResult(
+ "Jive", "Jun.", "Beg.", "1", "1", 3, "Jun.", "Adv."
+ ),
]
}
dict2 = {
person: [
- worker.CompetitionResult('Rumba', 'Kin.', 'Adv.', '2', '2', 2, 'Kin.', 'Adv.'),
- worker.CompetitionResult('Jive', 'Hgr', 'Beg.', '1', '1', 3, 'Hgr', 'Adv.')
+ worker.CompetitionResult(
+ "Rumba", "Kin.", "Adv.", "2", "2", 2, "Kin.", "Adv."
+ ),
+ worker.CompetitionResult("Jive", "Hgr", "Beg.", "1", "1", 3, "Hgr", "Adv."),
]
}
- cases = (
- dict1 | fixture_consolidateGroups[0],
- dict2 | fixture_consolidateGroups[0]
- )
+ cases = (dict1 | fixture_consolidateGroups[0], dict2 | fixture_consolidateGroups[0])
return cases[request.param]
+
def test_consolidateGroups(fixture_consolidateGroups):
data = fixture_consolidateGroups[0]
dataWorker = worker.DataWorker()
@@ -183,7 +303,8 @@ def test_consolidateGroups(fixture_consolidateGroups):
assert dataWorker.consolidateGroups(data) == fixture_consolidateGroups[1]
for person in data:
- assert person.group == fixture_consolidateGroups[2].get(person, 'Kin.')
+ assert person.group == fixture_consolidateGroups[2].get(person, "Kin.")
+
def test_consolidateGroups_failing(fixture_consolidateGroups_fail):
data = fixture_consolidateGroups_fail
@@ -192,105 +313,143 @@ def test_consolidateGroups_failing(fixture_consolidateGroups_fail):
with pytest.raises(Exception):
dataWorker.consolidateGroups(data)
+
def test_createHtmlLUT(mocker):
- mock = mocker.patch('solo_turnier.html_parser.HtmlParser.guessDataFromHtmlTitle')
- mock.side_effect= [
- {'group': 'group1', 'class_': 'class1', 'dance': 'dance1'},
- {'group': 'group2', 'class_': 'class2', 'dance': 'dance2'},
- {'group': 'group3', 'class_': 'class3', 'dance': 'dance3'},
+ mock = mocker.patch("solo_turnier.html_parser.HtmlParser.guessDataFromHtmlTitle")
+ mock.side_effect = [
+ {"group": "group1", "class_": "class1", "dance": "dance1"},
+ {"group": "group2", "class_": "class2", "dance": "dance2"},
+ {"group": "group3", "class_": "class3", "dance": "dance3"},
]
-
- importMock1 = mocker.patch('solo_turnier.html_parser.HtmlImport')
- importMock2 = mocker.patch('solo_turnier.html_parser.HtmlImport')
- importMock3 = mocker.patch('solo_turnier.html_parser.HtmlImport')
- importMock1.title = 'Fake title 1'
- importMock2.title = 'Fake title 2'
- importMock3.title = 'Fake title 3'
+
+ importMock1 = mocker.patch("solo_turnier.html_parser.HtmlImport")
+ importMock2 = mocker.patch("solo_turnier.html_parser.HtmlImport")
+ importMock3 = mocker.patch("solo_turnier.html_parser.HtmlImport")
+ importMock1.title = "Fake title 1"
+ importMock2.title = "Fake title 2"
+ importMock3.title = "Fake title 3"
dataWorker = worker.DataWorker()
structure = dataWorker._createHtmlLUT([importMock1, importMock2, importMock3])
expected = {
- ('group1', 'class1', 'dance1'): importMock1,
- ('group2', 'class2', 'dance2'): importMock2,
- ('group3', 'class3', 'dance3'): importMock3,
+ ("group1", "class1", "dance1"): importMock1,
+ ("group2", "class2", "dance2"): importMock2,
+ ("group3", "class3", "dance3"): importMock3,
}
assert expected == structure
+
def test_mergeHtmlData(mocker):
- person1 = worker.ResultPerson('Max 1', 'Mustermann', 'TSC Entenhausen')
- person2 = worker.ResultPerson('Max 2', 'Mustermann', 'TSC Entenhausen')
- person3 = worker.ResultPerson('Max 3', 'Mustermann', 'TSC Entenhausen')
- person4 = worker.ResultPerson('Max 4', 'Mustermann', 'TSC Entenhausen')
+ person1 = worker.ResultPerson("Max 1", "Mustermann", "TSC Entenhausen")
+ person2 = worker.ResultPerson("Max 2", "Mustermann", "TSC Entenhausen")
+ person3 = worker.ResultPerson("Max 3", "Mustermann", "TSC Entenhausen")
+ person4 = worker.ResultPerson("Max 4", "Mustermann", "TSC Entenhausen")
data = {
person1: [
- worker.CompetitionResult('Rumba', 'Kin.', 'Beg.', '1', '1', 1, 'Kin./Jun.', 'Beg.'),
- worker.CompetitionResult('Cha Cha', 'Kin.', 'Adv.', '1', '1', 1, 'Kin.', 'Adv.'),
- worker.CompetitionResult('Jive', 'Kin.', 'Beg.', '1', '2', 1, 'Kin.', 'Beg.'),
- worker.CompetitionResult('Langs. Walzer', 'Kin.', 'Beg.', '1', '1', 1, 'Kin.', 'Newc./Beg.'),
+ worker.CompetitionResult(
+ "Rumba", "Kin.", "Beg.", "1", "1", 1, "Kin./Jun.", "Beg."
+ ),
+ worker.CompetitionResult(
+ "Cha Cha", "Kin.", "Adv.", "1", "1", 1, "Kin.", "Adv."
+ ),
+ worker.CompetitionResult(
+ "Jive", "Kin.", "Beg.", "1", "2", 1, "Kin.", "Beg."
+ ),
+ worker.CompetitionResult(
+ "Langs. Walzer", "Kin.", "Beg.", "1", "1", 1, "Kin.", "Newc./Beg."
+ ),
],
person2: [
- worker.CompetitionResult('Rumba', 'Kin.', 'Beg.', '2', '2', 2, 'Kin./Jun.', 'Beg.'),
- worker.CompetitionResult('Cha Cha', 'Kin.', 'Adv.', '2', '2', 2, 'Kin.', 'Adv.'),
- worker.CompetitionResult('Jive', 'Kin.', 'Beg.', '1', '2', 2, 'Kin.', 'Beg.'),
- worker.CompetitionResult('Langs. Walzer', 'Kin.', 'Newc.', '1', '1', 2, 'Kin.', 'Newc./Beg.'),
+ worker.CompetitionResult(
+ "Rumba", "Kin.", "Beg.", "2", "2", 2, "Kin./Jun.", "Beg."
+ ),
+ worker.CompetitionResult(
+ "Cha Cha", "Kin.", "Adv.", "2", "2", 2, "Kin.", "Adv."
+ ),
+ worker.CompetitionResult(
+ "Jive", "Kin.", "Beg.", "1", "2", 2, "Kin.", "Beg."
+ ),
+ worker.CompetitionResult(
+ "Langs. Walzer", "Kin.", "Newc.", "1", "1", 2, "Kin.", "Newc./Beg."
+ ),
],
person3: [
- worker.CompetitionResult('Rumba', 'Jun.', 'Beg.', '1', '1', 3, 'Kin./Jun.', 'Beg.'),
+ worker.CompetitionResult(
+ "Rumba", "Jun.", "Beg.", "1", "1", 3, "Kin./Jun.", "Beg."
+ ),
# worker.CompetitionResult('Cha Cha', 'Jun.', 'Adv.', '1', '1', 3, 'Kin.', 'Adv.'),
# worker.CompetitionResult('Jive', 'Jun.', 'Beg.', '2', '2', 3, 'Kin.', 'Beg.'),
# worker.CompetitionResult('Langs. Walzer', 'Jun.', 'Newc./Beg.', '1', '1', 3, 'Kin.', 'Beg.'),
],
person4: [
- worker.CompetitionResult('Rumba', 'Kin.', 'Beg.', '3', '3', 4, 'Kin./Jun.', 'Beg.'),
+ worker.CompetitionResult(
+ "Rumba", "Kin.", "Beg.", "3", "3", 4, "Kin./Jun.", "Beg."
+ ),
# worker.CompetitionResult('Cha Cha', 'Kin.', 'Adv.', '1', '1', 4, 'Kin.', 'Adv.'),
# worker.CompetitionResult('Jive', 'Kin.', 'Beg.', '2', '2', 4, 'Kin.', 'Beg.'),
# worker.CompetitionResult('Langs. Walzer', 'Kin.', 'Newc./Beg.', '1', '1', 4, 'Kin.', 'Beg.'),
],
}
- htmlParticipant1Dance1 = html_parser.HtmlParticipant('Max 1 Mustermann', '1.', True)
- htmlParticipant1Dance2 = html_parser.HtmlParticipant('Max 1 Mustermann', '1.', True)
- htmlParticipant1Dance3 = html_parser.HtmlParticipant('Max 1 Mustermann', '1.-2.', True)
- htmlParticipant1Dance4 = html_parser.HtmlParticipant('Max 1 Mustermann', '1.', True)
-
- htmlParticipant2Dance1 = html_parser.HtmlParticipant('Max 2 Mustermann', '2.', True)
- htmlParticipant2Dance2 = html_parser.HtmlParticipant('Max 2 Mustermann', '2.', True)
- htmlParticipant2Dance3 = html_parser.HtmlParticipant('Max 2 Mustermann', '1.-2.', True)
- htmlParticipant2Dance4 = html_parser.HtmlParticipant('Max 2 Mustermann', '1.', True)
-
- htmlParticipant3Dance1 = html_parser.HtmlParticipant('Max 3 Mustermann', '1.', True)
+ htmlParticipant1Dance1 = html_parser.HtmlParticipant("Max 1 Mustermann", "1.", True)
+ htmlParticipant1Dance2 = html_parser.HtmlParticipant("Max 1 Mustermann", "1.", True)
+ htmlParticipant1Dance3 = html_parser.HtmlParticipant(
+ "Max 1 Mustermann", "1.-2.", True
+ )
+ htmlParticipant1Dance4 = html_parser.HtmlParticipant("Max 1 Mustermann", "1.", True)
- htmlParticipant4Dance1 = html_parser.HtmlParticipant('Max 4 Mustermann', '3.', False)
+ htmlParticipant2Dance1 = html_parser.HtmlParticipant("Max 2 Mustermann", "2.", True)
+ htmlParticipant2Dance2 = html_parser.HtmlParticipant("Max 2 Mustermann", "2.", True)
+ htmlParticipant2Dance3 = html_parser.HtmlParticipant(
+ "Max 2 Mustermann", "1.-2.", True
+ )
+ htmlParticipant2Dance4 = html_parser.HtmlParticipant("Max 2 Mustermann", "1.", True)
+
+ htmlParticipant3Dance1 = html_parser.HtmlParticipant("Max 3 Mustermann", "1.", True)
+
+ htmlParticipant4Dance1 = html_parser.HtmlParticipant(
+ "Max 4 Mustermann", "3.", False
+ )
htmlParticipantsDance1 = {
- '1': htmlParticipant1Dance1,
- '2': htmlParticipant2Dance1,
- '3': htmlParticipant3Dance1,
- '4': htmlParticipant4Dance1
+ "1": htmlParticipant1Dance1,
+ "2": htmlParticipant2Dance1,
+ "3": htmlParticipant3Dance1,
+ "4": htmlParticipant4Dance1,
}
htmlParticipantsDance2 = {
- '1': htmlParticipant1Dance2,
- '2': htmlParticipant2Dance2,
+ "1": htmlParticipant1Dance2,
+ "2": htmlParticipant2Dance2,
}
htmlParticipantsDance3 = {
- '1': htmlParticipant1Dance3,
- '2': htmlParticipant2Dance3,
+ "1": htmlParticipant1Dance3,
+ "2": htmlParticipant2Dance3,
}
htmlParticipantsDance4 = {
- '1': htmlParticipant1Dance4,
- '2': htmlParticipant2Dance4,
+ "1": htmlParticipant1Dance4,
+ "2": htmlParticipant2Dance4,
}
- htmlCompetition1 = html_parser.HtmlImport('ETW, Solos Kin./Jun. Beginner Rumba', htmlParticipantsDance1)
- htmlCompetition2 = html_parser.HtmlImport('ETW, Solos Kin. Advanced Cha Cha', htmlParticipantsDance2)
- htmlCompetition3 = html_parser.HtmlImport('ETW, Solos Kinder Beginner Jive', htmlParticipantsDance3)
- htmlCompetition4 = html_parser.HtmlImport('ETW, Solos Kin. Newc./Beg. Langs. Walzer', htmlParticipantsDance4)
-
+ htmlCompetition1 = html_parser.HtmlImport(
+ "ETW, Solos Kin./Jun. Beginner Rumba", htmlParticipantsDance1
+ )
+ htmlCompetition2 = html_parser.HtmlImport(
+ "ETW, Solos Kin. Advanced Cha Cha", htmlParticipantsDance2
+ )
+ htmlCompetition3 = html_parser.HtmlImport(
+ "ETW, Solos Kinder Beginner Jive", htmlParticipantsDance3
+ )
+ htmlCompetition4 = html_parser.HtmlImport(
+ "ETW, Solos Kin. Newc./Beg. Langs. Walzer", htmlParticipantsDance4
+ )
+
dataWorker = worker.DataWorker()
- dataWorker.mergeHtmlData(data, [htmlCompetition1, htmlCompetition2, htmlCompetition3, htmlCompetition4])
+ dataWorker.mergeHtmlData(
+ data, [htmlCompetition1, htmlCompetition2, htmlCompetition3, htmlCompetition4]
+ )
person1Finalist = [c.finalist for c in data[person1]]
person2Finalist = [c.finalist for c in data[person2]]
@@ -306,33 +465,59 @@ def test_mergeHtmlData(mocker):
person1: [True, True, True, True],
person2: [True, True, True, True],
person3: [True],
- person4: [False],
+ person4: [False],
}
assert finalists == expectedFinalists
+
@pytest.fixture(params=range(4))
def fixture_getAllDancesInCompetition(request, mocker):
def mockCompetition(comp):
def mockUser():
- return mocker.patch('solo_turnier.worker.ResultPerson')
+ return mocker.patch("solo_turnier.worker.ResultPerson")
+
def mockDances(dances):
def mockDance(name):
- mock = mocker.patch('solo_turnier.worker.CompetitionResult')
+ mock = mocker.patch("solo_turnier.worker.CompetitionResult")
mock.dance = name
return mock
+
return [mockDance(d) for d in dances]
+
return {mockUser(): mockDances(dances) for dances in comp}
-
+
cases = (
- ([['Samba']], ['Samba']),
- ([['Samba', 'Rumba'], ['Cha Cha']], ['Samba', 'Cha Cha', 'Rumba']),
- ([['Samba', 'Rumba'], ['Cha Cha', 'Tango', 'Langs. Walzer']], ['Samba', 'Cha Cha', 'Rumba', 'Langs. Walzer', 'Tango']),
- ([['Cha Cha', 'Rumba', 'Jive'], ['Quickstep', 'Tango', 'Wiener Walzer', 'Langs. Walzer'], ['Slowfox', 'Langs. Walzer', 'Paso Doble', 'Samba']], ['Samba', 'Cha Cha', 'Rumba', 'Paso Doble', 'Jive', 'Langs. Walzer', 'Tango', 'Wiener Walzer', 'Slowfox', 'Quickstep'])
+ ([["Samba"]], ["Samba"]),
+ ([["Samba", "Rumba"], ["Cha Cha"]], ["Samba", "Cha Cha", "Rumba"]),
+ (
+ [["Samba", "Rumba"], ["Cha Cha", "Tango", "Langs. Walzer"]],
+ ["Samba", "Cha Cha", "Rumba", "Langs. Walzer", "Tango"],
+ ),
+ (
+ [
+ ["Cha Cha", "Rumba", "Jive"],
+ ["Quickstep", "Tango", "Wiener Walzer", "Langs. Walzer"],
+ ["Slowfox", "Langs. Walzer", "Paso Doble", "Samba"],
+ ],
+ [
+ "Samba",
+ "Cha Cha",
+ "Rumba",
+ "Paso Doble",
+ "Jive",
+ "Langs. Walzer",
+ "Tango",
+ "Wiener Walzer",
+ "Slowfox",
+ "Quickstep",
+ ],
+ ),
)
case = cases[request.param]
return (mockCompetition(case[0]), case[1])
+
def test_getAllDancesInCompetitions(fixture_getAllDancesInCompetition):
print(fixture_getAllDancesInCompetition)
data = fixture_getAllDancesInCompetition[0]
@@ -340,57 +525,76 @@ def test_getAllDancesInCompetitions(fixture_getAllDancesInCompetition):
ret = dataWorker.getAllDancesInCompetitions(data)
assert ret == fixture_getAllDancesInCompetition[1]
+
def test_collectPersonsInGroups(mocker):
def mockPerson(group):
- mock = mocker.patch('solo_turnier.worker.ResultPerson')
+ mock = mocker.patch("solo_turnier.worker.ResultPerson")
mock.group = group
return mock
+
persons = (
- mockPerson('Kin.'), mockPerson('Kin.'), mockPerson('Jun.'),
- mockPerson('Kin.'), mockPerson(None), mockPerson('Jug.'),
- mockPerson(None), mockPerson('Kin./Jun.'), mockPerson('Jun.')
+ mockPerson("Kin."),
+ mockPerson("Kin."),
+ mockPerson("Jun."),
+ mockPerson("Kin."),
+ mockPerson(None),
+ mockPerson("Jug."),
+ mockPerson(None),
+ mockPerson("Kin./Jun."),
+ mockPerson("Jun."),
)
data = {p: [] for p in persons}
dataWorker = worker.DataWorker()
groups = dataWorker.collectPersonsInGroups(data)
- assert groups['Kin.'] == [persons[0], persons[1], persons[3]]
- assert groups['Jun.'] == [persons[2], persons[8]]
- assert groups['Jug.'] == [persons[5]]
- assert groups['Sonst'] == [persons[4], persons[6], persons[7]]
+ assert groups["Kin."] == [persons[0], persons[1], persons[3]]
+ assert groups["Jun."] == [persons[2], persons[8]]
+ assert groups["Jug."] == [persons[5]]
+ assert groups["Sonst"] == [persons[4], persons[6], persons[7]]
+
def test_sortPersons_withId(mocker):
def mockPerson(id):
- mock = mocker.patch('solo_turnier.worker.ResultPerson')
+ mock = mocker.patch("solo_turnier.worker.ResultPerson")
mock.id = id
return mock
+
persons = [mockPerson(2), mockPerson(1), mockPerson(5), mockPerson(3)]
dataWorker = worker.DataWorker()
sorted, showIds = dataWorker.sortPersonsInGroup(persons)
assert sorted == [persons[1], persons[0], persons[3], persons[2]]
assert showIds == True
+
def test_sortPersons_withoutId(mocker):
def mockPerson(name):
- mock = mocker.patch('solo_turnier.worker.ResultPerson')
+ mock = mocker.patch("solo_turnier.worker.ResultPerson")
mock.id = 3
mock.name = name
- mock.club = 'TSC Entenhausen'
+ mock.club = "TSC Entenhausen"
return mock
- persons = [mockPerson('Max'), mockPerson('Isabel'), mockPerson('Reimund'), mockPerson('Anna')]
+
+ persons = [
+ mockPerson("Max"),
+ mockPerson("Isabel"),
+ mockPerson("Reimund"),
+ mockPerson("Anna"),
+ ]
persons[2].id = None
dataWorker = worker.DataWorker()
sorted, showIds = dataWorker.sortPersonsInGroup(persons)
assert sorted == [persons[3], persons[1], persons[0], persons[2]]
assert showIds == False
+
def test_mapPersonResultsToDanceList(mocker):
def mockResult(dance):
- mock = mocker.patch('solo_turnier.worker.CompetitionResult')
+ mock = mocker.patch("solo_turnier.worker.CompetitionResult")
mock.dance = dance
return mock
- dances = ['Cha Cha', 'Rumba', 'Langs. Walzer', 'Quickstep']
- results = [mockResult('Rumba'), mockResult('Quickstep'), mockResult('Cha Cha')]
+
+ dances = ["Cha Cha", "Rumba", "Langs. Walzer", "Quickstep"]
+ results = [mockResult("Rumba"), mockResult("Quickstep"), mockResult("Cha Cha")]
dataWorker = worker.DataWorker()
mappedResults = dataWorker.mapPersonResultsToDanceList(results, dances)
assert mappedResults == [results[2], results[0], None, results[1]]
diff --git a/src/solo_turnier/types.py b/src/solo_turnier/types.py
deleted file mode 100644
index 94f08ac..0000000
--- a/src/solo_turnier/types.py
+++ /dev/null
@@ -1,349 +0,0 @@
-
-from . import group
-from . import competition_class
-
-class CSVResultRow:
- def __init__(self, firstName, lastName, club, id, group, class_, dance, place, placeTo, competitionGroup, competitionClass):
- self.firstName = firstName
- self.lastName = lastName
- self.name = f'{firstName} {lastName}'
- self.club = club
- self.id = id
- self.group = group
- self.class_ = class_
- self.dance = dance
- self.place = place
- self.placeTo = placeTo
- self.competitionGroup = competitionGroup
- self.competitionClass = competitionClass
-
- def __repr__(self):
- return f'{self.name} ({self.id}, {self.club}) is in {self.group} {self.class_} and danced the {self.dance} in {self.competitionGroup} {self.competitionClass} getting place {self.place}-{self.placeTo}'
-
-class HtmlPreviewParticipant:
- def __init__(self, name, id, group_):
- self.name = name
- self.id = id
- groupParser = group.GroupParser()
- self.group = groupParser.parseClass(group_)
- self.finalist = None
-
- def __eq__(self, o):
- if type(o) != HtmlPreviewParticipant:
- return False
-
- return all(map(lambda x, y: x == y, (self.name, self.id, self.group), (o.name, o.id, o.group)))
-
- def __repr__(self):
- return f'{self.id} ({self.name}, {self.group})'
-
- def __hash__(self):
- return hash((self.id, self.name, self.group))
-
- def __gt__(self, other):
- return self.id >= other.id
-
-class HtmlParticipant:
- def __init__(self, name, id):
- self.name = name
- self.id = id
- self.finalist = None
-
- def __eq__(self, o):
- if type(o) != HtmlPreviewParticipant:
- return False
-
- return all(map(lambda x, y: x == y, (self.name, self.id, self.group), (o.name, o.id, o.group)))
-
- def __repr__(self):
- return f'{self.id}: {self.name}'
-
- def __hash__(self):
- return hash((self.id, self.name))
-
-# class PreviewParticipationData:
-# def __init__(self, dance: str, class_: competition_class.CompetitionClass):
-# self.class_ = class_
-# self.dance = dance
-
-class HtmlPreviewImport:
- def __init__(
- self,
- participants: dict[int, list[HtmlPreviewParticipant]],
- results: dict[HtmlPreviewParticipant, dict[str, competition_class.CompetitionClass]]
- ):
- self.participants = participants
- self.results = results
-
- def __repr__(self):
- return (str(self.participants), str(self.results))
-
-class HtmlResultImport:
- def __init__(self, results: dict[HtmlParticipant, str]):
- self.results = results
-
- def __repr__(self):
- return str(self.results)
-
-class HtmlResultTotalTable:
- def __init__(self, participants):
- self.participants = participants
-
- def __repr__(self):
- return str(self.participants)
-
-class HtmlCompetitionResultRow:
- def __init__(self, name, id, dance, group, class_, place, placeTo, finalist):
- self.dance = dance
- self.group = group
- self.class_ = class_
- self.place = place
- self.placeTo = placeTo
- self.id = int(id)
- self.name = name
- self.finalist = finalist
-
- def __repr__(self):
- if self.place == self.placeTo:
- result = f'{self.place}.'
- else:
- result = f'{self.place}.-{self.placeTo}.'
-
- if self.finalist == True:
- finalist = '[F]'
- else:
- finalist = ''
- return f'Result[{self.id}]({self.group} {self.class_} {self.dance} as {result}{finalist})'
-
- def __eq__(self, o):
- if not isinstance(o, CompetitionResult):
- return False
-
- return (
- self.dance == o.dance and
- self.competitionClass == o.competitionClass and
- self.competitionGroup == o.competitionGroup and
- self.place == o.place and self.placeTo == o.placeTo and
- self.id == o.id
- )
-
-class HtmlSingleCompetitionResult:
- def __init__(self, name, place, placeTo, finalist):
- self.name = name
- self.place = place
- self.placeTo = placeTo
- self.finalist = finalist
-
- def __repr__(self):
- if self.placeTo is None:
- place = self.place
- else:
- place = f'{self.place}-{self.placeTo}'
-
- if self.finalist:
- return f'Res({self.name} [F], placed {place})'
- else:
- return f'Res({self.name}, placed {place})'
-
-class HtmlCompetitionTotalResults:
- def __init__(self):
- self.results = {}
- self.tabges = {}
-
- def __getTuple(self, group: group.Group_t, class_: competition_class.Class_t, dance: str, id: int):
- return (group, class_, dance, id)
-
- def get(self, group: group.Group_t, class_: competition_class.Class_t, dance: str, id: int) -> list[HtmlSingleCompetitionResult]:
- return self.results[self.__getTuple(group, class_, dance, id)]
-
- def getById(self, id: int) -> dict[tuple[str, group.Group_t, competition_class.Class_t], HtmlSingleCompetitionResult]:
- ret = {}
-
- for k in self.results:
- if int(k[3]) != id:
- continue
- # ret = ret + self.results[k]
- # Dance, Group, Class
- key = (k[2], k[0], k[1])
- ret[key] = self.results[k]
-
- return ret
-
- def add(self, group, class_, dance, id, result: HtmlSingleCompetitionResult):
- tup = self.__getTuple(group, class_, dance, id)
- l = self.results.get(tup, [])
- l.append(result)
- self.results[tup] = l
-
-class SingleParticipantResult:
- def __init__(
- self,
- competitionClass: competition_class.Class_t,
- nativeClass: competition_class.CompetitionClass,
- dance: str,
- finalist: bool,
- place: int,
- placeTo: int|None
- ):
- self.competitionClass = competitionClass
- self.nativeClass = nativeClass
- self.dance = dance
- self.finalist = finalist
- self.place = place
- self.placeTo = placeTo
-
- if placeTo == place:
- self.placeTo = None
-
- self.placeNative = None
- self.placeNativeTo = None
-
- def __repr__(self):
- asFinalist = ' as finalist' if self.finalist else ''
-
- if self.placeTo is None:
- return f'SR[{self.place} in {self.dance} {self.competitionClass} ({self.placeNative}-{self.placeNativeTo}, {self.nativeClass}){asFinalist}]'
-
- return f'SR[{self.place}-{self.placeTo} in {self.dance} {self.competitionClass} ({self.placeNative}-{self.placeNativeTo}, {self.nativeClass}){asFinalist}]'
-
- def getPlace(self):
- if self.placeTo is None:
- return f'{self.place}.'
- else:
- return f'{self.place}.-{self.placeTo}.'
-
- def getNativePlace(self):
- if self.placeNativeTo is None:
- return f'{self.placeNative}.'
- else:
- return f'{self.placeNative}.-{self.placeNativeTo}.'
-
-class TotalGroupResult:
- def __init__(self, dances: list[str], results: dict[HtmlPreviewParticipant, list[SingleParticipantResult]]):
- self.dances = dances
- self.results = results
- def __repr__(self):
- return f'TotalGroupResult({self.dances}, {self.results})'
-
-class State4:
- def __init__(
- self,
- resultPerGroup: dict[group.Group, TotalGroupResult]
- ):
- parser = group.GroupParser()
- self.groups = parser.getGroupsAsSortedList(resultPerGroup.keys())
- self.results = resultPerGroup
-
-class State3:
- def __init__(
- self,
- previewImport: HtmlPreviewImport,
- htmlResults: HtmlCompetitionTotalResults
- ):
- self.previewImport = previewImport
- self.htmlResults = htmlResults
-
-class Participant:
- def __init__(self, firstName: str, lastName: str, club: str, group: group.Group, class_: competition_class.CompetitionClass):
- self.firstName = firstName
- self.lastName = lastName
- self.club = club
- self.group = group
- self.class_ = class_
-
-class ParticipantResult:
- def __init__(
- self, id: int, finalist: bool, cancelled: bool,
- group: group.Group_t,
- class_: competition_class.Class_t,
- dance: str,
- place, placeTo
- ):
- self.id = id
- self.finalist = finalist
- self.cancelled = cancelled
- self.group = group
- self.class_ = class_
- self.dance = dance
- self.place = place
- self.placeTo = placeTo
-
-class Stage2:
- def __init__(self, results: dict[Participant, list[ParticipantResult]]):
- self.results = results
-
-class TableCompetitionEntry:
- def __init__(
- self,
- cancelled: bool,
- finalist: bool,
- class_: competition_class.Class_t,
- place: int = -1,
- placeTo: int = -1,
- group: group.Group_t = None,
- id: int = None
- ):
- self.finalist = finalist
- self.cancelled = cancelled
- self.group = group
- self.class_ = class_
- self.place = place
- self.placeTo = placeTo
-
- def __repr__(self):
- def paramMerging(l):
- return ', '.join(filter(lambda x: x is not None, l))
-
- if self.cancelled:
- params = paramMerging([self.group, self.class_, self.id])
- if len(params) > 0:
- return f'- ({params})'
- else:
- return '-'
- elif not self.finalist:
- params = paramMerging([self.group, self.class_, self.id])
- if len(params) > 0:
- return f'x ({params})'
- else:
- return 'x'
- else:
- if self.place == self.placeTo:
- place = f'{self.place}.'
- else:
- place = f'{self.place}.-{self.placeTo}.'
- params = paramMerging([self.group, self.class_, self.id])
- return f'{place} ({params})'
-
-class TableEntry:
- def __init__(self, competitions: list[TableCompetitionEntry]):
- self.competitions = competitions
-
- def __repr__(self):
- return ', '.join(self.competitions)
-
-class TableRow:
- def __init__(
- self,
- participant: Participant,
- id: int,
- entries: list[TableEntry]
- ):
- self.participant = participant
- self.id = id
- self.entries = entries
-
- def getRowList(self):
- if self.id is not None:
- first = f'{self.id}. {self.participant.firstName} {self.participant.lastName} ({self.participant.club})'
- else:
- first = f'{self.participant.firstName} {self.participant.lastName} ({self.participant.club})'
- return [first] + map(str, self.entries)
-
-class OutputTable:
- def __init__(self, dances: list[str], rows: list[TableRow]):
- self.dances = dances
- self.rows = rows
-
-class Stage1:
- def __init__(self, tables: dict[group.Group, OutputTable]):
- self.tables = tables
diff --git a/src/solo_turnier/types/__init__.py b/src/solo_turnier/types/__init__.py
new file mode 100644
index 0000000..b1aa540
--- /dev/null
+++ b/src/solo_turnier/types/__init__.py
@@ -0,0 +1,18 @@
+from .place import Place
+
+from .person import Person
+from .htmlPreviewParticipant import HtmlPreviewParticipant
+from .htmlParticipant import HtmlParticipant
+from .htmlResultImport import HtmlResultImport
+from .htmlResultTotalTable import HtmlResultTotalTable
+from .htmlCompetitionResultRow import HtmlCompetitionResultRow
+from .competitionTuple import CompetitionTuple
+from .htmlSingleCompetitionResult import HtmlSingleCompetitionResult
+from .htmlSingleCompetitionFixture import HtmlSingleCompetitionFixture
+from .htmlCompetitionTotalResults import HtmlCompetitionTotalResults
+from .singleParticipantResult import SingleParticipantResult
+from .totalGroupResult import TotalGroupResult
+from .participant import Participant
+from .tableData import *
+
+from .stages import *
diff --git a/src/solo_turnier/types/competitionTuple.py b/src/solo_turnier/types/competitionTuple.py
new file mode 100644
index 0000000..947355f
--- /dev/null
+++ b/src/solo_turnier/types/competitionTuple.py
@@ -0,0 +1,27 @@
+import solo_turnier
+
+
+class CompetitionTuple:
+ def __init__(
+ self,
+ group: solo_turnier.group.Group_t,
+ class_: solo_turnier.competition_class.Class_t,
+ dance: str,
+ id: int,
+ ):
+ self.group = group
+ self.class_ = class_
+ self.dance = dance
+ self.id = id
+
+ def __hash__(self):
+ return hash(("Tuple", self.group, self.class_, self.dance, self.id))
+
+ def __repr__(self):
+ return f"T({self.group},{self.class_},{self.dance},{self.id})"
+
+ def __eq__(self, other):
+ if type(other) != type(self):
+ return False
+
+ return self.__hash__() == other.__hash__()
diff --git a/src/solo_turnier/types/htmlCompetitionResultRow.py b/src/solo_turnier/types/htmlCompetitionResultRow.py
new file mode 100644
index 0000000..ca89be2
--- /dev/null
+++ b/src/solo_turnier/types/htmlCompetitionResultRow.py
@@ -0,0 +1,35 @@
+class HtmlCompetitionResultRow:
+ def __init__(self, name, id, dance, group, class_, place, placeTo, finalist):
+ self.dance = dance
+ self.group = group
+ self.class_ = class_
+ self.place = place
+ self.placeTo = placeTo
+ self.id = int(id)
+ self.name = name
+ self.finalist = finalist
+
+ def __repr__(self):
+ if self.place == self.placeTo:
+ result = f"{self.place}."
+ else:
+ result = f"{self.place}.-{self.placeTo}."
+
+ if self.finalist == True:
+ finalist = "[F]"
+ else:
+ finalist = ""
+ return f"Result[{self.id}]({self.group} {self.class_} {self.dance} as {result}{finalist})"
+
+ def __eq__(self, o):
+ if not isinstance(o, CompetitionResult):
+ return False
+
+ return (
+ self.dance == o.dance
+ and self.competitionClass == o.competitionClass
+ and self.competitionGroup == o.competitionGroup
+ and self.place == o.place
+ and self.placeTo == o.placeTo
+ and self.id == o.id
+ )
diff --git a/src/solo_turnier/types/htmlCompetitionTotalResults.py b/src/solo_turnier/types/htmlCompetitionTotalResults.py
new file mode 100644
index 0000000..fb46b0e
--- /dev/null
+++ b/src/solo_turnier/types/htmlCompetitionTotalResults.py
@@ -0,0 +1,51 @@
+import solo_turnier
+from .htmlSingleCompetitionResult import HtmlSingleCompetitionResult
+from .competitionTuple import CompetitionTuple
+
+
+class HtmlCompetitionTotalResults:
+ def __init__(self):
+ self.results = {}
+ self.fixups = {}
+
+ def __getTuple(
+ self,
+ group: solo_turnier.group.Group_t,
+ class_: solo_turnier.competition_class.Class_t,
+ dance: str,
+ id: int,
+ ):
+ return CompetitionTuple(group, class_, dance, id)
+
+ def get(
+ self,
+ group: solo_turnier.group.Group_t,
+ class_: solo_turnier.competition_class.Class_t,
+ dance: str,
+ id: int,
+ ) -> list[HtmlSingleCompetitionResult]:
+ return self.results[self.__getTuple(group, class_, dance, id)]
+
+ def getById(
+ self, id: int
+ ) -> dict[
+ tuple[str, solo_turnier.group.Group_t, solo_turnier.competition_class.Class_t],
+ HtmlSingleCompetitionResult,
+ ]:
+ ret = {}
+
+ for k in self.results:
+ if int(k.id) != id:
+ continue
+ # ret = ret + self.results[k]
+ # Dance, Group, Class
+ key = (k.dance, k.group, k.class_)
+ ret[key] = self.results[k]
+
+ return ret
+
+ def add(self, group, class_, dance, id: int, result: HtmlSingleCompetitionResult):
+ tup = self.__getTuple(group, class_, dance, id)
+ l = self.results.get(tup, [])
+ l.append(result)
+ self.results[tup] = l
diff --git a/src/solo_turnier/types/htmlParticipant.py b/src/solo_turnier/types/htmlParticipant.py
new file mode 100644
index 0000000..8502d6e
--- /dev/null
+++ b/src/solo_turnier/types/htmlParticipant.py
@@ -0,0 +1,26 @@
+class HtmlParticipant:
+ def __init__(self, name, id):
+ self.name = name
+ self.id = id
+ self.finalist = None
+
+ def __eq__(self, o):
+ if type(o) != HtmlParticipant:
+ return False
+
+ return all(
+ map(
+ lambda x, y: x == y,
+ (self.name, self.id, self.group),
+ (o.name, o.id, o.group),
+ )
+ )
+
+ def __repr__(self):
+ return f"{self.id}: {self.name}"
+
+ def __hash__(self):
+ return hash((self.id, self.name))
+
+ def __gt__(self, other):
+ return self.id >= other.id
diff --git a/src/solo_turnier/types/htmlPreviewParticipant.py b/src/solo_turnier/types/htmlPreviewParticipant.py
new file mode 100644
index 0000000..b072e67
--- /dev/null
+++ b/src/solo_turnier/types/htmlPreviewParticipant.py
@@ -0,0 +1,28 @@
+class HtmlPreviewParticipant:
+ def __init__(self, name, id, group_):
+ self.name = name
+ self.id = id
+ groupParser = group.GroupParser()
+ self.group = groupParser.parseGroup(group_)
+ self.finalist = None
+
+ def __eq__(self, o):
+ if type(o) != HtmlPreviewParticipant:
+ return False
+
+ return all(
+ map(
+ lambda x, y: x == y,
+ (self.name, self.id, self.group),
+ (o.name, o.id, o.group),
+ )
+ )
+
+ def __repr__(self):
+ return f"{self.id} ({self.name}, {self.group})"
+
+ def __hash__(self):
+ return hash((self.id, self.name, self.group))
+
+ def __gt__(self, other):
+ return self.id >= other.id
diff --git a/src/solo_turnier/types/htmlResultImport.py b/src/solo_turnier/types/htmlResultImport.py
new file mode 100644
index 0000000..de9a49c
--- /dev/null
+++ b/src/solo_turnier/types/htmlResultImport.py
@@ -0,0 +1,9 @@
+from .htmlParticipant import HtmlParticipant
+
+
+class HtmlResultImport:
+ def __init__(self, results: dict[HtmlParticipant, str]):
+ self.results = results
+
+ def __repr__(self):
+ return str(self.results)
diff --git a/src/solo_turnier/types/htmlResultTotalTable.py b/src/solo_turnier/types/htmlResultTotalTable.py
new file mode 100644
index 0000000..c5952f7
--- /dev/null
+++ b/src/solo_turnier/types/htmlResultTotalTable.py
@@ -0,0 +1,6 @@
+class HtmlResultTotalTable:
+ def __init__(self, participants):
+ self.participants = participants
+
+ def __repr__(self):
+ return str(self.participants)
diff --git a/src/solo_turnier/types/htmlSingleCompetitionFixture.py b/src/solo_turnier/types/htmlSingleCompetitionFixture.py
new file mode 100644
index 0000000..fbd2334
--- /dev/null
+++ b/src/solo_turnier/types/htmlSingleCompetitionFixture.py
@@ -0,0 +1,17 @@
+from .place import Place
+import solo_turnier
+
+
+class HtmlSingleCompetitionFixture:
+ def __init__(
+ self,
+ place: Place,
+ group: solo_turnier.group.Group,
+ class_: solo_turnier.competition_class.CompetitionClass,
+ ):
+ self.place = place
+ self.group = group
+ self.class_ = class_
+
+ def __repr__(self):
+ return f"Fix({self.place},{self.group},{self.class_})"
diff --git a/src/solo_turnier/types/htmlSingleCompetitionResult.py b/src/solo_turnier/types/htmlSingleCompetitionResult.py
new file mode 100644
index 0000000..175aca0
--- /dev/null
+++ b/src/solo_turnier/types/htmlSingleCompetitionResult.py
@@ -0,0 +1,25 @@
+from .place import Place
+
+
+class HtmlSingleCompetitionResult:
+ def __init__(self, name: str, place: Place, finalist: bool):
+ self.name = name
+ self.place = place
+ self.finalist = finalist
+
+ def __repr__(self):
+ place = self.place
+
+ if self.finalist:
+ return f"Res({self.name} [F], placed {place})"
+ else:
+ return f"Res({self.name}, placed {place})"
+
+ def __gt__(self, other):
+ return self.id > other.id
+
+ def __eq__(self, other):
+ return self.id == other.id
+
+ def __hash__(self):
+ return hash(self.id)
diff --git a/src/solo_turnier/types/participant.py b/src/solo_turnier/types/participant.py
new file mode 100644
index 0000000..8c6efd8
--- /dev/null
+++ b/src/solo_turnier/types/participant.py
@@ -0,0 +1,23 @@
+import solo_turnier
+
+from .person import Person
+
+
+class Participant(Person):
+ def __init__(
+ self,
+ name: str,
+ id: int,
+ finalist: bool = None,
+ ):
+ super().__init__(name)
+ self.id = id
+ self.finalist = finalist
+
+ def __repr__(self):
+ if self.finalist == True:
+ return f"Part({self.id} {self.name},F)"
+ return f"Part({self.id} {self.name})"
+
+ def __gt__(self, other):
+ return self.id > other.id
diff --git a/src/solo_turnier/types/person.py b/src/solo_turnier/types/person.py
new file mode 100644
index 0000000..5c7b20e
--- /dev/null
+++ b/src/solo_turnier/types/person.py
@@ -0,0 +1,3 @@
+class Person:
+ def __init__(self, name: str):
+ self.name = name
diff --git a/src/solo_turnier/types/place.py b/src/solo_turnier/types/place.py
new file mode 100644
index 0000000..60621ef
--- /dev/null
+++ b/src/solo_turnier/types/place.py
@@ -0,0 +1,10 @@
+class Place:
+ def __init__(self, place: int, placeTo: int | None = None):
+ self.place = place
+ self.placeTo = placeTo
+
+ def __repr__(self):
+ if self.placeTo is None:
+ return f"{self.place}."
+
+ return f"{self.place}.-{self.placeTo}."
diff --git a/src/solo_turnier/types/singleParticipantResult.py b/src/solo_turnier/types/singleParticipantResult.py
new file mode 100644
index 0000000..969fe88
--- /dev/null
+++ b/src/solo_turnier/types/singleParticipantResult.py
@@ -0,0 +1,29 @@
+import solo_turnier
+
+from .place import Place
+
+
+class SingleParticipantResult:
+ def __init__(
+ self,
+ competitionClass: solo_turnier.competition_class.Class_t,
+ nativeClass: solo_turnier.competition_class.CompetitionClass,
+ dance: str,
+ finalist: bool,
+ place: Place,
+ nativePlace: Place = None,
+ ):
+ self.competitionClass = competitionClass
+ self.nativeClass = nativeClass
+ self.dance = dance
+ self.finalist = finalist
+ self.place = place
+ self.nativePlace = nativePlace
+
+ def __repr__(self):
+ asFinalist = " as finalist" if self.finalist else ""
+
+ return f"SR[{self.place} in {self.dance} {self.competitionClass} ({self.nativePlace} {self.nativeClass}){asFinalist}]"
+
+ def getNativePlace(self) -> str:
+ return str(self.nativePlace)
diff --git a/src/solo_turnier/types/stages.py b/src/solo_turnier/types/stages.py
new file mode 100644
index 0000000..dbad5d2
--- /dev/null
+++ b/src/solo_turnier/types/stages.py
@@ -0,0 +1,29 @@
+import solo_turnier
+
+from .totalGroupResult import TotalGroupResult
+from .htmlCompetitionTotalResults import HtmlCompetitionTotalResults
+from .singleParticipantResult import SingleParticipantResult
+from .participant import Participant
+from .tableData import GroupTableData
+
+NullableGroup = solo_turnier.group.Group | None
+
+
+class Stage5:
+ def __init__(
+ self,
+ resultsPerGroup: dict[NullableGroup, GroupTableData],
+ ):
+ self.resultsPerGroup = resultsPerGroup
+
+
+class State4:
+ def __init__(self, resultPerGroup: dict[NullableGroup, TotalGroupResult]):
+ parser = solo_turnier.group.GroupParser()
+ self.groups = parser.getGroupsAsSortedList(resultPerGroup.keys())
+ self.results = resultPerGroup
+
+
+class State3:
+ def __init__(self, htmlResults: HtmlCompetitionTotalResults):
+ self.htmlResults = htmlResults
diff --git a/src/solo_turnier/types/tableData.py b/src/solo_turnier/types/tableData.py
new file mode 100644
index 0000000..c4478eb
--- /dev/null
+++ b/src/solo_turnier/types/tableData.py
@@ -0,0 +1,11 @@
+import solo_turnier
+from .participant import Participant
+from .singleParticipantResult import SingleParticipantResult
+
+SortedResultList = dict[Participant, list[SingleParticipantResult | None]]
+
+
+class GroupTableData:
+ def __init__(self, dances: list[str], results: SortedResultList):
+ self.dances = dances
+ self.resultsInGroup = results
diff --git a/src/solo_turnier/types/totalGroupResult.py b/src/solo_turnier/types/totalGroupResult.py
new file mode 100644
index 0000000..ac1e6c7
--- /dev/null
+++ b/src/solo_turnier/types/totalGroupResult.py
@@ -0,0 +1,15 @@
+from .singleParticipantResult import SingleParticipantResult
+from .participant import Participant
+
+
+class TotalGroupResult:
+ def __init__(
+ self,
+ dances: list[str],
+ results: dict[Participant, list[SingleParticipantResult]],
+ ):
+ self.dances = dances
+ self.results = results
+
+ def __repr__(self):
+ return f"TotalGrR({self.dances}, {self.results})"
diff --git a/src/solo_turnier/worker.py b/src/solo_turnier/worker.py
index b5aa7ba..127c681 100644
--- a/src/solo_turnier/worker.py
+++ b/src/solo_turnier/worker.py
@@ -1,24 +1,12 @@
-import logging
-from pprint import pformat
-
-import re
-
-import solo_turnier
-from solo_turnier import html_parser
-from .reader import ResultRow
-from .types import HtmlCompetitionResultRow as CompetitionResult
-from . import types
-from . import competition_class
-
class HtmlPerson:
def __init__(self, name, id, group):
self.name = name
self.id = id
self.group = group
-
+
def __repr__(self):
- return f'{self.name} ({self.id}, {self.group})'
-
+ return f"{self.name} ({self.id}, {self.group})"
+
def __eq__(self, o):
if not isinstance(o, HtmlPerson):
return False
@@ -27,671 +15,33 @@ class HtmlPerson:
def __hash__(self):
return str(self).__hash__()
+
class ResultPerson:
- def __init__(self, firstName, lastName, club, id = None, group = None):
+ def __init__(self, firstName, lastName, club, id=None, group=None):
self.firstName = firstName
self.lastName = lastName
- self.name = f'{firstName} {lastName}'
+ self.name = f"{firstName} {lastName}"
self.club = club
self.id = id
self.group = group
-
- @staticmethod
- def extractFromResultRow(row: ResultRow):
- return ResultPerson(
- firstName=row.firstName,
- lastName=row.lastName,
- club=row.club
- )
-
+
def __eq__(self, o):
if not isinstance(o, ResultPerson):
return False
-
+
return (
- self.firstName == o.firstName and
- self.lastName == o.lastName and
- self.club == o.club and
- self.id == o.id
+ self.firstName == o.firstName
+ and self.lastName == o.lastName
+ and self.club == o.club
+ and self.id == o.id
)
-
+
def __repr__(self):
if self.id is None:
- return f'{self.name} ({self.club})'
+ return f"{self.name} ({self.club})"
else:
- return f'{self.name} ({self.club}) [{self.id}]'
-
+ return f"{self.name} ({self.club}) [{self.id}]"
+
def __hash__(self):
text = str(self)
return text.__hash__()
-
-
-class ImportNotParsableException(Exception):
- pass
-
-ParserList_t = dict[str, html_parser.HtmlParser]
-
-class PreviewWorker:
- def __init__(self):
- self.l = logging.getLogger('solo_turnier.worker.PreviewWorker')
- self.participants = {}
- self.previewResults = {}
-
- def filterFilesPreview(self, files: list[str]) -> ParserList_t:
- self.l.debug('Filtering the list of parsers by removing all non preview entries.')
- ret = {}
- for file in files:
- with open(file, 'r') as fp:
- text = fp.read()
-
- parser = html_parser.HtmlParser(text, file)
-
- try:
- data = parser.guessDataFromHtmlTitle()
- except:
- self.l.error(f'Unable to parse html file in {file}. Please check manually.')
- continue
-
- if data['class_'] == 'Sichtung':
- self.l.debug(f"Found candidate in {file}. Adding to the list.")
- ret[file] = parser
- else:
- self.l.debug(f'Rejecting file {file} as the name {data["class_"]} did not match.')
-
- return ret
-
- def __extractPersonsFromSinglePreview(self, parser: html_parser.HtmlParser):
- imported = parser.parsePreparationRound()
- parser.cleanPreparationRoundImport(imported)
- data = imported['data']
-
- headerData = parser.guessDataFromHtmlTitle()
- dance = headerData['dance']
- classParser = solo_turnier.competition_class.CompetitionClassParser()
-
- def getRowIndexOfClass():
- return data['titles'].index('Platz von\nPlatz bis')
-
- self.l.log(5, data)
-
- if data['titles'][0] != 'Wertungsrichter':
- self.l.fatal('Cannot parse the parsed content of the preview file.')
- raise ImportNotParsableException('Incompatible export file')
-
- if data['titles'][-1] == 'Startgruppe':
- self.l.debug('Combined competition found. Extracting group from table required.')
- extractGroup = True
- else:
- self.l.debug('Using group from the title.')
- group = parser.guessDataFromHtmlTitle(imported['title'])['group']
- extractGroup = False
-
- classRowIndex = getRowIndexOfClass()
-
- for index, e in enumerate(data['table'][0]):
- if e['text'] == '':
- # Skip empty columns
- continue
-
- # Extract data from column
- name = e['meta']
- id = int(e['text'])
- if extractGroup:
- group = data['table'][-1][index]['text']
-
- # dance =
- class_ = classParser.parseClass(data['table'][classRowIndex][index]['text'])
-
- participant = types.HtmlPreviewParticipant(name, id, group)
-
- l = self.participants.get(id, [])
- self.l.log(5, 'Checking for existence of %s in %s: %s', participant, l, participant in l)
- if participant not in l:
- l.append(participant)
- self.participants[id] = l
-
- results = self.previewResults.get(participant, {})
- results[dance] = class_
- self.previewResults[participant] = results
-
- def importAllData(self, parsers: ParserList_t) -> types.HtmlPreviewImport:
- self.participants = {}
-
- for file in parsers:
- parser = parsers[file]
- try:
- self.__extractPersonsFromSinglePreview(parser)
- except:
- self.l.error('Failed to parse preview round in file %s. Skipping this file\'s content.', parser.fileName)
-
- return types.HtmlPreviewImport(self.participants, self.previewResults)
-
-class ResultExtractor:
-
- def __init__(self):
- self.l = logging.getLogger('solo_turnier.worker.ResultExtractor')
- self.rePlaceSingle = re.compile(' *([0-9]+) *')
- self.rePlaceDouble = re.compile(' *([0-9]+) *- *([0-9]+) *')
-
- def getAllParsers(self, files: list[tuple[str,str]]) -> ParserList_t:
- ret = {}
- classParser = competition_class.CompetitionClassParser()
-
- for filePair in files:
- with open(filePair[0], 'r') as fp:
- text = fp.read()
- parser = html_parser.HtmlParser(text, filePair[0])
-
- if filePair[1] is None:
- parserTab = None
- else:
- with open(filePair[1], 'r') as fp:
- textTab = fp.read()
- parserTab = html_parser.HtmlParser(textTab, filePair[1])
-
- try:
- data = parser.guessDataFromHtmlTitle()
- except:
- self.l.error('Cannot parse HTML file %s to check if it is a valid result. Check manually.', filePair[0])
- continue
-
- try:
- guessedClass = classParser.parseClass(data['class_'])
- except:
- self.l.error('Issue parsing class of file %s. Check manually.', filePair[0])
- continue
-
- self.l.debug('Fetched result data: %s, guessed class %s', data, guessedClass)
- ret[filePair] = (parser, parserTab)
-
- return ret
-
- def _extractPlace(self, placeStr: str):
- s = placeStr.replace('.', '')
-
- matches = self.rePlaceSingle.fullmatch(s)
- if matches is not None:
- return (int(matches.group(1)), None)
-
- matches = self.rePlaceDouble.fullmatch(s)
- if matches is not None:
- return (int(matches.group(1)), int(matches.group(2)))
-
- self.l.error('Could not parse place string "%s"', placeStr)
- raise Exception('Place cannot be parsed')
-
- def _analyzeSingleParser(self, parser: html_parser.HtmlParser, results: types.HtmlCompetitionTotalResults):
- data = parser.guessDataFromHtmlTitle()
- competitionClass = data['class_']
- competitionGroup = data['group']
- dance = data['dance']
-
- result = parser.parseResult()
- self.l.log(5, 'Raw data extracted: %s', result)
-
- for person in result.results.keys():
- placeStr = result.results[person]
- place, placeTo = self._extractPlace(placeStr)
- competitionResult = types.HtmlSingleCompetitionResult(person.name, place, placeTo, person.finalist)
- results.add(competitionGroup, competitionClass, dance, person.id, competitionResult)
- #
-
- def _analyzeIndividualResults(self, parser: html_parser.HtmlParser, results: types.HtmlCompetitionTotalResults):
- data = parser.guessDataFromHtmlTitle()
- competitionClass = data['class_']
- competitionGroup = data['group']
- dance = data['dance']
-
- result = parser.parseIndividualResult(competitionGroup, competitionClass, dance)
- self.l.log(5, 'Found individual results: %s', result.participants)
- results.tabges.update(result.participants)
-
- def extractAllData(self, parsers: ParserList_t) -> types.HtmlCompetitionTotalResults:
- ret = types.HtmlCompetitionTotalResults()
-
- for fileNameTuple in parsers:
- fileName = fileNameTuple[0]
- self.l.debug('Extracting data from file %s', fileName)
- self._analyzeSingleParser(parsers[fileNameTuple][0], ret)
-
- if parsers[fileNameTuple][1] is None:
- self.l.info('Skipping extraction of individual result as class is not yet finished.')
- else:
- self.l.debug('Fetching individual result of combined competitions in %s', fileName)
- self._analyzeIndividualResults(parsers[fileNameTuple][1], ret)
-
- return ret
-
-class DataWorker:
- def __init__(self):
- self.l = logging.getLogger('solo_turnier.worker.DataWorker')
-
- def combineRowsByPerson(self, rows: list[ResultRow]) -> dict[ResultPerson, list[CompetitionResult]]:
- ret = {}
- for row in rows:
- result = CompetitionResult.extractFromResultRow(row)
-
- if result.place == '-' or result.placeTo == '-':
- continue
-
- person = ResultPerson.extractFromResultRow(row)
- if person not in ret:
- ret[person] = []
- ret[person].append(result)
- return ret
-
- def checkUniqueIds(self, data: dict[ResultPerson, list[CompetitionResult]]) -> bool:
- unique = True
- for person in data:
- ids = set([c.id for c in data[person]])
- if len(ids) == 1:
- person.id = list(ids)[0]
- else:
- unique = False
-
- return unique
-
- """
- Return a tuple
- The first one is True, if all persons could be unambiguously identified a group
- The second one is True if there was the need to override a group but it was possible to extract from other data
- The second one can be seen as a warning
- """
- def consolidateGroups(self, data:dict[ResultPerson, list[CompetitionResult]]) -> tuple[bool, bool]:
- ambiguous = False
- warnChange = False
-
- unambiguousGroups = set(['Kin.', 'Jun.', 'Jug.'])
- combinations = set(['Kin./Jun.', 'Jun./Jug.'])
-
- for person in data:
- groupsRaw = set([c.group for c in data[person]])
-
- unknown = groupsRaw.difference(unambiguousGroups).difference(combinations)
- if len(unknown) > 0:
- raise Exception(f'There were unknown groups found for {person}: {unknown}')
-
- numUnambiguousGroups = len(groupsRaw.intersection(unambiguousGroups))
-
- if numUnambiguousGroups == 0:
- if len(groupsRaw) == 2:
- warnChange = True
- person.group = 'Jun.'
- else:
- ambiguous = True
- if len(groupsRaw) == 1:
- person.group = list(groupsRaw)[0]
-
- elif numUnambiguousGroups == 1:
- if len(groupsRaw.intersection(combinations)) > 0:
- warnChange = True
-
- person.group = list(groupsRaw.intersection(unambiguousGroups))[0]
-
- else:
- raise Exception(f'{person} cannot have different groups.')
-
- return (not ambiguous, warnChange)
-
- def _createHtmlLUT(self, htmlImports: list[html_parser.HtmlImport]):
- ret = {}
- parser = html_parser.HtmlParser('')
- for imp in htmlImports:
- parsed = parser.guessDataFromHtmlTitle(imp.title)
- key = (parsed['group'], parsed['class_'], parsed['dance'])
- ret[key] = imp
- self.l.debug('LUT[%s] = %s', key, imp)
- self.l.debug('LUT completed')
- return ret
-
- def mergeHtmlData(self, data:dict[ResultPerson, list[CompetitionResult]], htmlImports: list[html_parser.HtmlImport]):
- lut = self._createHtmlLUT(htmlImports)
-
- for person in data:
- for competition in data[person]:
- key = (competition.competitionGroup, competition.competitionClass, competition.dance)
- htmlImport = lut[key]
- participant = htmlImport.participants[str(competition.id)]
- if participant.name != person.name:
- self.l.error(f'Names for {person} and participant in HTML import ({participant}) do not match. Please check carefully.')
- competition.finalist = participant.finalist
-
- def getAllDancesInCompetitions(self, data:dict[ResultPerson, list[CompetitionResult]]) -> list[str]:
- allDances = [
- 'Samba', 'Cha Cha', 'Rumba', 'Paso Doble', 'Jive',
- 'Langs. Walzer', 'Tango', 'Wiener Walzer', 'Slowfox', 'Quickstep'
- ]
- dancesPresent = {d: False for d in allDances}
-
- for person in data:
- for competition in data[person]:
- dancesPresent[competition.dance] = True
-
- return [d for d in allDances if dancesPresent[d]]
-
- def collectPersonsInGroups(self, data:dict[ResultPerson, list[CompetitionResult]]) -> list[tuple[str, list[ResultPerson]]]:
- groups = {
- 'Kin.': [p for p in data.keys() if p.group == 'Kin.'],
- 'Jun.': [p for p in data.keys() if p.group == 'Jun.'],
- 'Jug.': [p for p in data.keys() if p.group == 'Jug.'],
- }
- found = groups['Kin.'] + groups['Jun.'] + groups['Jug.']
- groups['Sonst'] = [p for p in data.keys() if p not in found]
- return groups
-
- def sortPersonsInGroup(self, persons: list[ResultPerson]) -> list[ResultPerson]:
- ids = [p.id for p in persons]
-
- def decorateByName(p: ResultPerson):
- return (f'{p.name} ({p.club})', p)
- def decorateById(p: ResultPerson):
- return (p.id, p)
-
- if any([id == None for id in ids]):
- # We need to sort by name
- decorated = [decorateByName(p) for p in persons]
- showIds = False
- else:
- decorated = [decorateById(p) for p in persons]
- showIds = True
-
- decorated.sort()
-
- return ([d[1] for d in decorated], showIds)
-
- def mapPersonResultsToDanceList(self, results: list[CompetitionResult], dances: list[str]) -> list[CompetitionResult|None]:
- ret = []
- for dance in dances:
- competitions = [c for c in results if c.dance == dance]
- if len(competitions) == 0:
- ret.append(None)
- elif len(competitions) > 1:
- raise Exception(f'Multiple competitions with the same dance "{dance}" found.')
- else:
- ret.append(competitions[0])
-
- return ret
-
-class Worker:
- def __init__(self):
- self.l = logging.getLogger('solo_turnier.worker.Worker')
- self._allDances = (
- ['Samba', 'Cha Cha', 'Rumba', 'Paso Doble', 'Jive'] +
- ['Langs. Walzer', 'Tango', 'Wiener Walzer', 'Slowfox', 'Quickstep']
- )
-
- def collectAllData(
- self,
- htmlCandidatesPreview: list[str],
- htmlResultsFileNames: list[str]
- ) -> types.State3:
-
- previewWorker = PreviewWorker()
- self.l.info('Filtering for pure preview rounds.')
- parsers = previewWorker.filterFilesPreview(htmlCandidatesPreview)
- self.l.debug('Remaining files: %s', list(parsers.keys()))
-
- self.l.info('Extracting person data from the preview rounds.')
- previewImport = previewWorker.importAllData(parsers)
- self.l.debug('Total preview imported participants: %s', pformat(previewImport.participants))
- self.l.log(5, 'Total preview results: %s', pformat(previewImport.results))
-
- resultExtractor = ResultExtractor()
- resultParsers = resultExtractor.getAllParsers(htmlResultsFileNames)
- htmlResults = resultExtractor.extractAllData(resultParsers)
- self.l.info('Overall result data extracted: %s', pformat(htmlResults.results))
-
- return types.State3(previewImport, htmlResults)
-
- def combineData(self, importedData: types.State3):
- self.l.info('Starting to build data sets.')
- groups = self._extractGroups(importedData)
- self.l.debug('Found groups in the dataset: %s', groups)
-
- totalResult = {}
-
- for group in groups:
- self.l.debug('Collecting data for total result of group %s', group)
-
- dances = self._extractDancesPerGroup(importedData, group)
- self.l.log(5, 'Found dances in group %s: %s', group, dances)
-
- participants = self._extractParticipantsPerGroup(importedData.previewImport, group)
- self.l.log(5, 'Related participants %s', participants)
-
- results = {}
-
- for participant in participants:
- self.l.log(5, 'Collecting data for %s', participant)
- resultsOfParticipant = self._getResultOfSingleParticipant(
- participant, group, importedData.previewImport,
- importedData.htmlResults, dances
- )
- self.l.log(5, 'Obtained result %s', resultsOfParticipant)
- results[participant] = resultsOfParticipant
-
- self.l.log(5, 'Result before native fixing: %s', pformat(results))
- # self._fixNativePlaces(dances, results)
- self._fixNativeDataFromTable(dances, results, importedData.htmlResults)
- self.l.log(5, 'Result after native fixing: %s', pformat(results))
- # self.l.log(5,'Fixed data %s', results)
-
- totalResult[group] = types.TotalGroupResult(dances, results)
-
- self.l.log(5, 'Total result of all groups: %s', pformat(totalResult))
-
- ret = types.State4(totalResult)
- return ret
-
-
- def _extractGroups(self, data: types.State3):
- groupSet = set([])
- for id in data.previewImport.participants:
- participants = data.previewImport.participants[id]
- for participant in participants:
- groupSet.add(participant.group)
-
- self.l.log(5, 'Set of active groups: %s', groupSet)
- groupParser = solo_turnier.group.GroupParser()
- groups = groupParser.getGroupsAsSortedList(groupSet)
- return groups
-
- def _extractDancesPerGroup(self, data: types.State3, group: solo_turnier.group.Group):
- dances = set()
- additionalDances = set()
- for part in data.previewImport.results.keys():
- allFoundDances = set(data.previewImport.results[part].keys())
- dances.update(allFoundDances.intersection(self._allDances))
- additionalDances.update(allFoundDances.difference(self._allDances))
-
- if len(additionalDances) > 0:
- self.l.warning('There were dances found, that are not registered. A bug? The dances were: %s', additionalDances)
-
- dancesList = [x for x in self._allDances if x in dances]
- additionalDancesList = list(additionalDances)
- additionalDancesList.sort()
- return dancesList + additionalDancesList
-
- def _extractParticipantsPerGroup(
- self,
- previewData: types.HtmlPreviewImport,
- group: solo_turnier.group.Group
- ) -> list[types.HtmlPreviewParticipant]:
- ret = []
- for id in previewData.participants:
- participantList = previewData.participants[id]
- for participant in participantList:
- if participant.group == group:
- ret.append(participant)
- return ret
-
- def _getResultOfSingleParticipant(
- self,
- participant: types.HtmlPreviewParticipant,
- nominalGroup: solo_turnier.group.Group,
- previewResults: types.HtmlPreviewImport,
- totalResults: types.HtmlCompetitionTotalResults,
- allDances: list[str]
- ) -> list[types.SingleParticipantResult|None]:
- rawResults = totalResults.getById(participant.id)
- self.l.log(5, 'Found result data (raw): %s', rawResults)
-
- results = [None for x in allDances]
-
- for danceIdx, dance in enumerate(allDances):
- # self.l.log(5, '%s %s', dance, danceIdx)
- def getResult() -> types.SingleParticipantResult|None:
- for key in rawResults:
- if key[0] != dance:
- continue
- rawResult = rawResults[key]
-
- if len(rawResult) != 1:
- raise Exception('Multiple results found with same key')
- rawResult = rawResult[0]
-
- nativeClass = key[2]
- # nativeClass = previewResults.results[participant][dance]
- # nativeClass = key[2]
-
- # self.l.log(5, 'Result %s => %s', key, rawResult)
- ret = types.SingleParticipantResult(
- key[2], nativeClass, dance, rawResult.finalist,
- rawResult.place, rawResult.placeTo
- )
-
- return ret
- return None
-
- results[danceIdx] = getResult()
-
- return results
-
- def _fixNativeDataFromTable(
- self,
- dances: list[str],
- data: dict[types.HtmlPreviewParticipant, list[types.SingleParticipantResult]],
- importedData: types.HtmlCompetitionTotalResults
- ):
- rePlace = re.compile('([0-9]+)(?:-([0-9]+))?')
- classParser = competition_class.CompetitionClassParser()
-
- for participant in data.keys():
- self.l.log(5, 'fixing participant %s', participant)
- results = data[participant]
- for result in results:
- if result is None:
- continue
- self.l.log(5, 'Looking at result set %s', result)
-
- def selectEntry(k):
- return k[2] == result.dance and int(k[3]) == participant.id
-
- keys = list(importedData.tabges.keys())
- selected = list(map(selectEntry, keys))
- selectedIndex = selected.index(True)
-
- raw = importedData.tabges[keys[selectedIndex]]
- self.l.log(5,'Raw %s', raw)
- nativePlaceRaw = raw[0]
- matcher = rePlace.fullmatch(nativePlaceRaw)
- if matcher is None:
- self.l.error('Cannot parse place string %s for participant %u (%s) in dance %s', nativePlaceRaw, participant.id, participant, result.dance)
- continue
- self.l.log(5, 'Found strings by regex: %s', matcher.groups())
- result.placeNative = matcher.group(1)
- result.placeNativeTo = matcher.group(2)
-
- if raw[1] is not None:
- result.nativeClass = classParser.parseAbbreviatedClass(raw[1])
-
- pass
-
- def _fixNativePlaces(
- self,
- dances: list[str],
- data: dict[types.HtmlPreviewParticipant, list[types.SingleParticipantResult]]
- ):
- classParser = solo_turnier.competition_class.CompetitionClassParser()
- allClasses = classParser.getAllClasses()
- allClasses.reverse()
-
- for class_ in allClasses:
- for danceIdx, dance in enumerate(dances):
- self.l.log(5, 'Fixing native places for class %s in dance %s', class_, dance)
-
- remainingParticipants = []
-
- for participant in data.keys():
- results = data[participant]
- danceResult = results[danceIdx]
-
- if danceResult is None:
- continue
-
- # self.l.log(5, 'Result of dance: %s', danceResult)
-
- if classParser.isABetterThanB(danceResult.nativeClass, class_):
- # self.l.log(5, 'Skipping %s as the native class is higher', participant)
- continue
-
- remainingParticipants.append((danceResult.place, participant.id, participant))
-
- remainingParticipants.sort()
- # self.l.log(5, 'Remaining participants %s', remainingParticipants)
-
- def getAllParticipantsWithSamePlace():
- first = remainingParticipants.pop(0)
- ret = [first]
- while len(remainingParticipants) > 0 and remainingParticipants[0][0] == first[0]:
- ret.append(remainingParticipants.pop(0))
- return ret
-
- def updateNativePlaces(samePlaced, placeStart):
- nextPlace = placeStart + len(samePlaced)
- if len(samePlaced) == 1:
- placeTo = None
- else:
- placeTo = nextPlace - 1
-
- for p in samePlaced:
- data[p[2]][danceIdx].placeNative = placeStart
- data[p[2]][danceIdx].placeNativeTo = placeTo
-
- return nextPlace
-
- places = list(map(lambda x: x[0], remainingParticipants))
- place = 1
- while len(remainingParticipants) > 0:
- samePlaced = getAllParticipantsWithSamePlace()
- place = updateNativePlaces(samePlaced, place)
-
- # self.l.log(5, '(Partially) fixed places: %s', (data))
-
- def filterOutFinalists(self, data: types.State4, filterOut: bool):
- for group in data.results:
- self.l.debug('Cleaning up group %s', group.name)
- participants = data.results[group].results.keys()
- droppedParticipants = []
-
- for participant in participants:
- self.l.debug('Checking %s', participant)
-
- def isFinalistInDance(x: types.HtmlSingleCompetitionResult|None):
- if x is None:
- return False
- return x.finalist
- mapped = list(map(isFinalistInDance, data.results[group].results[participant]))
- finalist = True in mapped
- self.l.log(5,'Check for finalist (in dances %s): %s', mapped, finalist)
-
- if finalist:
- participant.finalist = True
- else:
- participant.finalist = False
- self.l.warning('Dropping %s from the output as no finalist', participant)
- droppedParticipants.append(participant)
-
- if filterOut:
- for droppedParticipant in droppedParticipants:
- data.results[group].results.pop(droppedParticipant)
diff --git a/src/solo_turnier/workers/DataWorker.py b/src/solo_turnier/workers/DataWorker.py
new file mode 100644
index 0000000..8c77d30
--- /dev/null
+++ b/src/solo_turnier/workers/DataWorker.py
@@ -0,0 +1,47 @@
+from ..worker import ResultPerson
+from ..types import HtmlCompetitionResultRow as CompetitionResult
+from solo_turnier import html_parser
+import logging
+
+
+class DataWorker:
+ def __init__(self):
+ self.l = logging.getLogger("solo_turnier.worker.DataWorker")
+
+ def sortPersonsInGroup(self, persons: list[ResultPerson]) -> list[ResultPerson]:
+ ids = [p.id for p in persons]
+
+ def decorateByName(p: ResultPerson):
+ return (f"{p.name} ({p.club})", p)
+
+ def decorateById(p: ResultPerson):
+ return (p.id, p)
+
+ if any([id == None for id in ids]):
+ # We need to sort by name
+ decorated = [decorateByName(p) for p in persons]
+ showIds = False
+ else:
+ decorated = [decorateById(p) for p in persons]
+ showIds = True
+
+ decorated.sort()
+
+ return ([d[1] for d in decorated], showIds)
+
+ def mapPersonResultsToDanceList(
+ self, results: list[CompetitionResult], dances: list[str]
+ ) -> list[CompetitionResult | None]:
+ ret = []
+ for dance in dances:
+ competitions = [c for c in results if c.dance == dance]
+ if len(competitions) == 0:
+ ret.append(None)
+ elif len(competitions) > 1:
+ raise Exception(
+ f'Multiple competitions with the same dance "{dance}" found.'
+ )
+ else:
+ ret.append(competitions[0])
+
+ return ret
diff --git a/src/solo_turnier/workers/OutputShaper.py b/src/solo_turnier/workers/OutputShaper.py
new file mode 100644
index 0000000..c06c2c5
--- /dev/null
+++ b/src/solo_turnier/workers/OutputShaper.py
@@ -0,0 +1,58 @@
+import solo_turnier
+import logging
+
+
+class OutputShaper:
+ def __init__(self):
+ self.l = logging.getLogger("solo_turnier.worker.OutputShaper")
+
+ def shapeResults(
+ self, results: solo_turnier.types.State4
+ ) -> solo_turnier.types.Stage5:
+ ret = {}
+ for group in results.results:
+ ret[group] = self._handleGroup(results.results[group])
+
+ return solo_turnier.types.Stage5(ret)
+
+ def _handleGroup(
+ self,
+ totalGroupResult: solo_turnier.types.TotalGroupResult,
+ ) -> solo_turnier.types.GroupTableData:
+ sortedResultList = {}
+ for participant in totalGroupResult.results:
+ sortedResultList[participant] = [None for x in totalGroupResult.dances]
+
+ for result in totalGroupResult.results[participant]:
+ if result.dance not in totalGroupResult.dances:
+ self.l.error(
+ "Result in unknown dance found in table. This is a bug. (%s)",
+ result,
+ )
+ continue
+
+ idx = totalGroupResult.dances.index(result.dance)
+ sortedResultList[participant][idx] = result
+ return solo_turnier.types.GroupTableData(
+ totalGroupResult.dances, sortedResultList
+ )
+
+ def _reshapeRow(
+ self,
+ results: list[solo_turnier.types.SingleParticipantResult],
+ dances: list[str],
+ ) -> list[solo_turnier.types.SingleParticipantResult]:
+ ret = [None for x in dances]
+
+ for result in results:
+ if result.dance not in dances:
+ self.l.error(
+ "Result in unknown dance found in table. This is a bug. (%s)",
+ result,
+ )
+ continue
+
+ idx = dances.index(result.dance)
+ ret[idx] = result
+
+ return ret
diff --git a/src/solo_turnier/workers/ResultExtractor.py b/src/solo_turnier/workers/ResultExtractor.py
new file mode 100644
index 0000000..507794d
--- /dev/null
+++ b/src/solo_turnier/workers/ResultExtractor.py
@@ -0,0 +1,127 @@
+from solo_turnier import html_parser
+from .. import types
+import logging
+import re
+from .. import competition_class
+
+ParserList_t = dict[str, html_parser.HtmlParser]
+
+
+class ResultExtractor:
+ def __init__(self):
+ self.l = logging.getLogger("solo_turnier.worker.ResultExtractor")
+ self.rePlaceSingle = re.compile(" *([0-9]+) *")
+ self.rePlaceDouble = re.compile(" *([0-9]+) *- *([0-9]+) *")
+
+ def getAllParsers(self, files: list[tuple[str, str]]) -> ParserList_t:
+ ret = {}
+ classParser = competition_class.CompetitionClassParser()
+
+ for filePair in files:
+ with open(filePair[0], "r") as fp:
+ text = fp.read()
+ parser = html_parser.HtmlParser(text, filePair[0])
+
+ if filePair[1] is None:
+ parserTab = None
+ else:
+ with open(filePair[1], "r") as fp:
+ textTab = fp.read()
+ parserTab = html_parser.HtmlParser(textTab, filePair[1])
+
+ try:
+ data = parser.guessDataFromHtmlTitle()
+ except:
+ self.l.error(
+ "Cannot parse HTML file %s to check if it is a valid result. Check manually.",
+ filePair[0],
+ )
+ continue
+
+ guessedClass = data["class_"]
+
+ self.l.debug(
+ "Fetched result data: %s, guessed class %s", data, guessedClass
+ )
+ ret[filePair] = (parser, parserTab)
+
+ return ret
+
+ def _extractPlace(self, placeStr: str) -> types.Place:
+ s = placeStr.replace(".", "")
+
+ matches = self.rePlaceSingle.fullmatch(s)
+ if matches is not None:
+ return types.Place(int(matches.group(1)))
+ # return (int(matches.group(1)), None)
+
+ matches = self.rePlaceDouble.fullmatch(s)
+ if matches is not None:
+ return types.Place(int(matches.group(1)), int(matches.group(2)))
+ # return (int(matches.group(1)), int(matches.group(2)))
+
+ self.l.error('Could not parse place string "%s"', placeStr)
+ raise Exception("Place cannot be parsed")
+
+ def _analyzeSingleParser(
+ self, parser: html_parser.HtmlParser, results: types.HtmlCompetitionTotalResults
+ ):
+ data = parser.guessDataFromHtmlTitle()
+ competitionClass = data["class_"]
+ competitionGroup = data["group"]
+ dance = data["dance"]
+
+ result = parser.parseResult()
+ self.l.log(5, "Raw data extracted: %s", result)
+
+ for person in result.results.keys():
+ placeStr = result.results[person]
+ place = self._extractPlace(placeStr)
+ competitionResult = types.HtmlSingleCompetitionResult(
+ person.name, place, person.finalist
+ )
+ results.add(
+ competitionGroup,
+ competitionClass,
+ dance,
+ int(person.id),
+ competitionResult,
+ )
+ #
+
+ def _analyzeResultFixups(
+ self, parser: html_parser.HtmlParser, results: types.HtmlCompetitionTotalResults
+ ):
+ data = parser.guessDataFromHtmlTitle()
+ competitionClass = data["class_"]
+ competitionGroup = data["group"]
+ dance = data["dance"]
+
+ resultFixups = parser.parseIndividualResult(
+ competitionGroup, competitionClass, dance
+ )
+ self.l.log(5, "Found additional result fixups: %s", resultFixups.participants)
+ results.fixups.update(resultFixups.participants)
+
+ def extractAllData(
+ self, parsers: ParserList_t
+ ) -> types.HtmlCompetitionTotalResults:
+ ret = types.HtmlCompetitionTotalResults()
+
+ for fileNameTuple in parsers:
+ fileName = fileNameTuple[0]
+ self.l.debug("Extracting data from file %s", fileName)
+ self._analyzeSingleParser(parsers[fileNameTuple][0], ret)
+
+ if parsers[fileNameTuple][1] is None:
+ self.l.info(
+ "Skipping extraction of individual result as class is not yet finished."
+ )
+ else:
+ self.l.debug(
+ "Fetching individual result of combined competitions in %s",
+ fileName,
+ )
+ self._analyzeResultFixups(parsers[fileNameTuple][1], ret)
+
+ return ret
diff --git a/src/solo_turnier/workers/Worker.py b/src/solo_turnier/workers/Worker.py
new file mode 100644
index 0000000..8cf04c9
--- /dev/null
+++ b/src/solo_turnier/workers/Worker.py
@@ -0,0 +1,546 @@
+import logging
+import solo_turnier
+from .. import types
+from .ResultExtractor import ResultExtractor
+from pprint import pformat
+import re
+from .. import competition_class
+
+
+class Worker:
+ def __init__(self):
+ self.l = logging.getLogger("solo_turnier.worker.Worker")
+ self._allDances = ["Samba", "Cha Cha", "Rumba", "Paso Doble", "Jive"] + [
+ "Langs. Walzer",
+ "Tango",
+ "Wiener Walzer",
+ "Slowfox",
+ "Quickstep",
+ ]
+ self._groupParser = solo_turnier.group.GroupParser()
+ self._classParser = solo_turnier.competition_class.CompetitionClassParser()
+
+ def collectAllData(self, htmlResultsFileNames: list[str]) -> types.State3:
+ resultExtractor = ResultExtractor()
+ resultParsers = resultExtractor.getAllParsers(htmlResultsFileNames)
+ htmlResults = resultExtractor.extractAllData(resultParsers)
+ self.l.log(5, "Overall result data extracted: %s", pformat(htmlResults.results))
+ self.l.log(
+ 5, "Overall result fixups extracted: %s", pformat(htmlResults.fixups)
+ )
+
+ return types.State3(htmlResults)
+
+ def combineData(self, importedData: types.State3):
+ self.l.info("Starting to build data sets.")
+
+ self.l.debug("Getting per participant groups")
+ groupMapping = self._getGroupMapping(importedData)
+ self.l.log(5, "ID-to-group mapping of the parsed data: %s", str(groupMapping))
+
+ groups = self._extractGroupsFromGroupMapping(groupMapping)
+ self.l.debug("Found groups in the dataset: %s", groups)
+
+ invertedGroupMapping = self._invertGroupMapping(groupMapping, groups)
+ self.l.log(5, "Inverted group maping: %s", invertedGroupMapping)
+
+ idToParticipantMapping = self._invertIdMapping(importedData.htmlResults)
+ self.l.log(5, "Id to participant mappting: %s", idToParticipantMapping)
+
+ totalResult = {}
+
+ for group in invertedGroupMapping:
+ self.l.debug("Collecting data for group %s", group)
+
+ participants = invertedGroupMapping[group]
+ self.l.log(5, "Participants in group: %s", participants)
+
+ tuplesInCurrentGroup = []
+
+ for participantId in participants:
+ tuplesInCurrentGroup.extend(
+ self._filterResultKeys(importedData.htmlResults, id=participantId)
+ )
+ self.l.log(
+ 5,
+ "Tuples of filtered in group %s: %s",
+ group,
+ list(tuplesInCurrentGroup),
+ )
+
+ dancesInGroup = self._extractAllDancesFromTuples(tuplesInCurrentGroup)
+ self.l.debug("Found dances in group %s: %s", group, dancesInGroup)
+
+ resultsInCurrentGroup = {}
+
+ for participantId in participants:
+ self.l.log(5, "Handling participant with ID %d", participantId)
+ # tuples = self._filterResultKeys(im)
+ participant = idToParticipantMapping[participantId]
+ self.l.log(5, "Participant in question: %s", participant)
+
+ participant.finalist = False
+
+ resultsInCurrentGroup[participant] = []
+
+ for tup in self._filterResultKeys(
+ importedData.htmlResults, id=participantId
+ ):
+ singleHtmlResultList = importedData.htmlResults.results[tup]
+ if len(singleHtmlResultList) > 1:
+ self.l.warning(
+ "More than one result per tuple (%s) found.", tup
+ )
+
+ singleHtmlResult = singleHtmlResultList[0]
+ singleResult = solo_turnier.types.SingleParticipantResult(
+ competitionClass=tup.class_,
+ nativeClass=tup.class_,
+ dance=tup.dance,
+ finalist=singleHtmlResult.finalist,
+ place=singleHtmlResult.place,
+ nativePlace=singleHtmlResult.place,
+ )
+
+ if tup in importedData.htmlResults.fixups:
+ fixup = importedData.htmlResults.fixups[tup]
+ self.l.log(
+ 5, "Fixture found for %s, %s: %s", participant, tup, fixup
+ )
+ self._applyFixture(singleResult, fixup)
+
+ resultsInCurrentGroup[participant].append(singleResult)
+
+ if singleHtmlResult.finalist:
+ participant.finalist = True
+
+ ###########################################################################################################################
+
+ totalGroupResult = types.TotalGroupResult(
+ dancesInGroup, resultsInCurrentGroup
+ )
+ self.l.log(5, "Total group result of group %s: %s", group, totalGroupResult)
+ totalResult[group] = totalGroupResult
+
+ ret = types.State4(totalResult)
+ return ret
+
+ for group in groups:
+ self.l.debug("Collecting data for total result of group %s", group)
+
+ dances = self._extractDancesPerGroup(importedData, group)
+ self.l.log(5, "Found dances in group %s: %s", group, dances)
+
+ participants = self._extractParticipantsPerGroup(importedData, group)
+ self.l.log(5, "Related participants %s", participants)
+
+ results = {}
+
+ for participant in participants:
+ self.l.log(5, "Collecting data for %s", participant)
+ resultsOfParticipant = self._getResultOfSingleParticipant(
+ participant,
+ group,
+ importedData.htmlResults,
+ dances,
+ )
+ self.l.log(5, "Obtained result %s", resultsOfParticipant)
+ results[participant] = resultsOfParticipant
+
+ self.l.log(5, "Result before native fixing: %s", pformat(results))
+ # self._fixNativePlaces(dances, results)
+ self._fixNativeDataFromTable(dances, results, importedData.htmlResults)
+ self.l.log(5, "Result after native fixing: %s", pformat(results))
+ # self.l.log(5,'Fixed data %s', results)
+
+ totalResult[group] = types.TotalGroupResult(dances, results)
+
+ self.l.log(5, "Total result of all groups: %s", pformat(totalResult))
+
+ return ret
+
+ def _extractGroups(self, data: types.State3):
+ groupSet = set([])
+ for tup in data.htmlResults.results.keys():
+ gr = self._groupParser.parseGroup(tup[0])
+ groupSet.update(gr.getContainedGroups())
+
+ self.l.log(5, "Set of active groups: %s", groupSet)
+ groups = self._groupParser.getGroupsAsSortedList(groupSet)
+ return groups
+
+ def _getGroupMapping(
+ self, importedData: types.State3
+ ) -> dict[int, solo_turnier.group.Group | None]:
+ def _getBestGroupGuess(groups, id):
+ counts = {}
+ grNones = 0
+ for gr in set(groups):
+ length = len(list(filter(lambda x: x == gr, groups)))
+ if isinstance(gr, tuple) or gr is None:
+ grNones = grNones + length
+ else:
+ counts[gr] = length
+ counts[None] = grNones
+ candidates = list(counts.keys())
+
+ def ccomp(i1):
+ return counts[i1]
+
+ candidates.sort(key=ccomp, reverse=True)
+
+ if len(candidates) == 1:
+ self.l.warning("Unrequired group guessing started.")
+ return candidates[0]
+ if len(candidates) == 0:
+ self.l.error("Problem during the group guessing triggered.")
+ return None
+
+ if counts[candidates[0]] > counts[candidates[1]]:
+ if candidates[0] is None:
+ self.l.error(
+ "Majority of guessed groups is ambiguous. Guessing failed for id %d. Falling back to second best guess %s.",
+ id,
+ candidates[1],
+ )
+ return candidates[1]
+
+ self.l.info("Using best fit %s for guessed group.", candidates[0])
+ return candidates[0]
+
+ self.l.warning("Group guessing failed.")
+ return None
+
+ groupsPerId = {}
+ for tup in importedData.htmlResults.results:
+ competitionGroup = tup.group
+ fixture = importedData.htmlResults.fixups.get(
+ tup, solo_turnier.types.HtmlSingleCompetitionFixture(None, None, None)
+ )
+ id = tup.id
+ if fixture.group is not None:
+ group = fixture.group
+ else:
+ containedGroups = competitionGroup.getContainedGroups()
+ if len(containedGroups) > 1:
+ self.l.error(
+ "The group for participant %d is ambiguous in (%s %s %s).",
+ id,
+ tup.group,
+ tup.class_,
+ tup.dance,
+ )
+ group = containedGroups
+ else:
+ group = competitionGroup
+
+ knownGroups = groupsPerId.get(id, [])
+ if group is not None:
+ knownGroups.append(group)
+ groupsPerId[id] = knownGroups
+
+ ret = {}
+ for id in groupsPerId.keys():
+ groupCandidates = groupsPerId[id]
+ groupSet = set(groupCandidates)
+
+ if len(groupSet) == 1:
+ ret[id] = groupSet.pop()
+ elif len(groupSet) > 1:
+ self.l.warning(
+ "Multiple groups for id %d found: %s", id, groupsPerId[id]
+ )
+ ret[id] = _getBestGroupGuess(groupCandidates, id)
+ else:
+ self.l.warning("No group for id %d could be found.", id)
+ ret[id] = None
+ return ret
+
+ def _extractGroupsFromGroupMapping(self, mapping):
+ foundGroups = set()
+ for id in mapping:
+ foundGroups.add(mapping[id])
+ sortedGroup = self._groupParser.getGroupsAsSortedList(foundGroups)
+ missingGroups = foundGroups.difference(sortedGroup)
+ sortedGroup = sortedGroup + list(missingGroups)
+ return sortedGroup
+
+ def _invertGroupMapping(self, mapping, groups):
+ ret = {}
+ for group in groups:
+ ret[group] = []
+ for id in mapping:
+ ret[mapping[id]].append(id)
+ for key in ret:
+ ret[key].sort()
+ return ret
+
+ def _filterResultKeys(
+ self,
+ results: solo_turnier.types.HtmlCompetitionTotalResults,
+ group: solo_turnier.group.Group_t | None = None,
+ class_: solo_turnier.competition_class.Class_t | None = None,
+ dance: str | None = None,
+ id: int | None = None,
+ ):
+ def checker(x: solo_turnier.types.CompetitionTuple) -> bool:
+ if group is not None and group != x.group:
+ return False
+ if class_ is not None and class_ != x.class_:
+ return False
+ if dance is not None and dance != x.dance:
+ return False
+ if id is not None and id != x.id:
+ return False
+ return True
+
+ return filter(checker, results.results.keys())
+
+ def _extractAllDancesFromTuples(
+ self, tuples: list[solo_turnier.types.CompetitionTuple]
+ ) -> list[str]:
+ danceSet = set()
+ danceSet.update(map(lambda x: x.dance, tuples))
+
+ # Check for unknown dances here
+ setDiff = danceSet.difference(self._allDances)
+ if len(setDiff) > 0:
+ self.l.warning(
+ "There are dances in the data set that are not known in the program. A bug?"
+ )
+ return [x for x in self._allDances if x in danceSet] + list(setDiff)
+
+ def _invertIdMapping(
+ self, htmlData: solo_turnier.types.HtmlCompetitionTotalResults
+ ):
+ mapping = {}
+ for tup in htmlData.results:
+ id = tup.id
+ results = htmlData.results[tup]
+ if len(results) > 1:
+ self.l.error(
+ "Non-unique results for tuple %s were found. Most probably this is a bug. The results are %s.",
+ tup,
+ results,
+ )
+ elif len(results) == 0:
+ self.l.error("No results for tuple %s found.", tup)
+ continue
+
+ if id not in mapping:
+ mapping[id] = solo_turnier.types.Participant(
+ name=results[0].name, id=id
+ )
+ else:
+ if mapping[id].name != results[0].name or mapping[id].id != id:
+ self.l.error(
+ "Invalid id to participant mapping found. The name of id has changed. Tuple was %s (values %s), mapping was %s",
+ tup,
+ results,
+ mapping[id],
+ )
+ return mapping
+
+ def _filterResultsById(
+ self, data: solo_turnier.types.HtmlCompetitionTotalResults, ids: list[int]
+ ):
+ ret = {}
+
+ return ret
+
+ def _applyFixture(
+ self,
+ singleResult: solo_turnier.types.SingleParticipantResult,
+ fixture: solo_turnier.types.HtmlSingleCompetitionFixture,
+ ):
+ singleResult.nativePlace = fixture.place
+
+ if fixture.class_ is not None:
+ singleResult.nativeClass = fixture.class_
+
+ def _extractDancesPerGroup(
+ self, data: types.State3, group: solo_turnier.group.Group
+ ):
+ dances = set()
+ additionalDances = set()
+ foundDances = set()
+ for tup in data.htmlResults.results.keys():
+ currentGroup = self._groupParser.parseGroup(tup[0])
+ if group not in currentGroup.getContainedGroups():
+ continue
+ foundDances.add(tup[2])
+
+ dances.update(foundDances.intersection(self._allDances))
+ additionalDances.update(foundDances.difference(self._allDances))
+
+ if len(additionalDances) > 0:
+ self.l.error(
+ "There were dances found, that are not registered. A bug? The dances were: %s",
+ additionalDances,
+ )
+
+ dancesList = [x for x in self._allDances if x in dances]
+ additionalDancesList = list(additionalDances)
+ additionalDancesList.sort()
+ return dancesList + additionalDancesList
+
+ def _extractParticipantsPerGroup(
+ self,
+ importedData: types.State3,
+ # previewData: types.HtmlPreviewImport,
+ group: solo_turnier.group.Group,
+ ) -> list[types.HtmlPreviewParticipant]:
+ ret = []
+
+ # self.l.log(5, 'Table %s', pformat(importedData.htmlResults.tabges))
+ # self.l.log(5, 'Results %s', pformat(importedData.htmlResults.results))
+
+ for tup in importedData.htmlResults.results.keys():
+ currentGroup = self._groupParser.parseGroup(tup[0])
+ activeGroups = currentGroup.getContainedGroups()
+ if group not in activeGroups:
+ continue
+
+ fixture = importedData.htmlResults.tabges.get(tup, None)
+ if fixture is None:
+ self.l.error("A fixture for the tuple %s could not be read.", tup)
+ else:
+ if (
+ fixture[2] is not None
+ and self._groupParser.parseGroup(fixture[2]) != group
+ ):
+ self.l.log(
+ 5,
+ "Skipping id %s in group %s as in other group.",
+ tup[3],
+ group,
+ )
+ continue
+
+ part = importedData.htmlResults.results[tup][0]
+ part.id = int(tup[3])
+ ret.append(part)
+
+ self.l.log(5, "ret %s", ret)
+ # raise Exception('Test')
+
+ # for id in previewData.participants:
+ # participantList = previewData.participants[id]
+ # for participant in participantList:
+ # if participant.group == group:
+ # ret.append(participant)
+ return ret
+
+ def _getResultOfSingleParticipant(
+ self,
+ participant: types.HtmlParticipant,
+ nominalGroup: solo_turnier.group.Group,
+ totalResults: types.HtmlCompetitionTotalResults,
+ allDances: list[str],
+ ) -> list[types.SingleParticipantResult | None]:
+ rawResults = totalResults.getById(participant.id)
+ self.l.log(
+ 5, "Found result data for id %i (raw): %s", participant.id, rawResults
+ )
+
+ results = [None for x in allDances]
+
+ for danceIdx, dance in enumerate(allDances):
+ # self.l.log(5, '%s %s', dance, danceIdx)
+ def getResult() -> types.SingleParticipantResult | None:
+ for key in rawResults:
+ if key[0] != dance:
+ continue
+ rawResult = rawResults[key]
+
+ if len(rawResult) != 1:
+ raise Exception("Multiple results found with same key")
+ rawResult = rawResult[0]
+
+ nativeClass = key[2]
+ # nativeClass = previewResults.results[participant][dance]
+ # nativeClass = key[2]
+
+ # self.l.log(5, 'Result %s => %s', key, rawResult)
+ ret = types.SingleParticipantResult(
+ key[2],
+ nativeClass,
+ dance,
+ rawResult.finalist,
+ rawResult.place,
+ rawResult.placeTo,
+ )
+
+ return ret
+ return None
+
+ results[danceIdx] = getResult()
+
+ return results
+
+ def _fixNativeDataFromTable(
+ self,
+ dances: list[str],
+ data: dict[types.HtmlPreviewParticipant, list[types.SingleParticipantResult]],
+ importedData: types.HtmlCompetitionTotalResults,
+ ):
+ rePlace = re.compile("([0-9]+)(?:-([0-9]+))?")
+ classParser = competition_class.CompetitionClassParser()
+
+ for participant in data.keys():
+ self.l.log(5, "fixing participant %s", participant)
+ results = data[participant]
+ for result in results:
+ if result is None:
+ continue
+ self.l.log(5, "Looking at result set %s", result)
+
+ def selectEntry(k):
+ return k[2] == result.dance and int(k[3]) == participant.id
+
+ keys = list(importedData.tabges.keys())
+ selected = list(map(selectEntry, keys))
+ try:
+ selectedIndex = selected.index(True)
+ except:
+ continue
+
+ raw = importedData.tabges[keys[selectedIndex]]
+ self.l.log(5, "Raw %s", raw)
+ nativePlaceRaw = raw[0]
+ matcher = rePlace.fullmatch(nativePlaceRaw)
+ if matcher is None:
+ self.l.error(
+ "Cannot parse place string %s for participant %u (%s) in dance %s",
+ nativePlaceRaw,
+ participant.id,
+ participant,
+ result.dance,
+ )
+ continue
+ self.l.log(5, "Found strings by regex: %s", matcher.groups())
+ result.placeNative = matcher.group(1)
+ result.placeNativeTo = matcher.group(2)
+
+ if raw[1] is not None:
+ result.nativeClass = classParser.parseAbbreviatedClass(raw[1])
+
+ pass
+
+ def filterOutFinalists(self, data: types.State4, filterOut: bool):
+ if filterOut:
+ for group in data.results:
+ groupName = "unknown" if group is None else group.name
+ self.l.debug("Cleaning up group %s", groupName)
+ participants = data.results[group].results.keys()
+ droppedParticipants = []
+
+ for participant in participants:
+ if participant.finalist == False:
+ self.l.info(
+ "Dropping %s from the output as no finalist", participant
+ )
+ droppedParticipants.append(participant)
+
+ for droppedParticipant in droppedParticipants:
+ data.results[group].results.pop(droppedParticipant)
diff --git a/src/solo_turnier/workers/__init__.py b/src/solo_turnier/workers/__init__.py
new file mode 100644
index 0000000..b5deb20
--- /dev/null
+++ b/src/solo_turnier/workers/__init__.py
@@ -0,0 +1,4 @@
+from . import ResultExtractor
+from . import DataWorker
+from . import Worker
+from . import OutputShaper