8 Commits

13 changed files with 240 additions and 20 deletions

5
Makefile Normal file
View File

@@ -0,0 +1,5 @@
all:
installer:
pynsist src/installer.cfg

View File

@@ -1,16 +1,20 @@
attrs==22.1.0
beautifulsoup4==4.11.1
blinker==1.6.2
certifi==2023.7.22
charset-normalizer==3.2.0
click==8.1.7
colorama==0.4.6
coloredlogs==15.0.1
coverage==6.5.0
debugpy==1.6.7
distlib==0.3.7
exceptiongroup==1.0.1
Flask==2.3.3
humanfriendly==10.0
idna==3.4
iniconfig==1.1.1
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.3
packaging==21.3
@@ -26,4 +30,5 @@ soupsieve==2.3.2.post1
tabulate==0.9.0
tomli==2.0.1
urllib3==2.0.5
Werkzeug==2.3.7
yarg==0.1.9

View File

@@ -1,6 +1,6 @@
[Application]
name=Solo Auswertung
version=0.9.0
version=0.9.4
# How to launch the app - this calls the 'main' function from the 'myapp' package:
entry_point=main:main
# icon=myapp.ico
@@ -18,22 +18,38 @@ console=true
# These must have wheels on PyPI:
pypi_wheels = attrs==22.1.0
beautifulsoup4==4.11.1
blinker==1.6.2
certifi==2023.7.22
charset-normalizer==3.2.0
click==8.1.7
colorama==0.4.6
coloredlogs==15.0.1
coverage==6.5.0
debugpy==1.6.7
distlib==0.3.7
exceptiongroup==1.0.1
Flask==2.3.3
humanfriendly==10.0
idna==3.4
iniconfig==1.1.1
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.3
packaging==21.3
pluggy==1.0.0
pynsist==2.8
pyparsing==3.0.9
pytest==7.2.0
pytest-cov==4.0.0
pytest-mock==3.10.0
requests==2.31.0
requests_download==0.1.2
soupsieve==2.3.2.post1
tabulate==0.9.0
tomli==2.0.1
urllib3==2.0.5
Werkzeug==2.3.7
yarg==0.1.9
packages = solo_turnier

View File

@@ -15,11 +15,24 @@ def main():
l = __initLogging()
cli = solo_turnier.cli.Cli(l)
batchWorker = solo_turnier.batch.BatchWorker(cli)
batchWorker.prepare()
if cli.showGUI():
raise Exception('Not yet implemented')
elif cli.startFlaskServer():
solo_turnier.flask.startFlask(
batchWorker,
debug=cli.getLogLevel() > 0,
port=cli.getPort()
)
else:
batchWorker = solo_turnier.batch.BatchWorker(cli)
batchWorker.run()
combinedData = batchWorker.run(
removeFilteredParicipants=not cli.showAllParticipants()
)
consoleOutputtter = solo_turnier.output.ConsoleOutputter()
consoleOutputtter.output(combinedData)
if __name__ == '__main__':
main()

View File

@@ -13,3 +13,4 @@ from . import worker
from . import output
from . import batch
from . import flask

View File

@@ -5,6 +5,8 @@ import os
import pprint
import tabulate
import tkinter.filedialog
import tkinter.simpledialog
class BatchWorker:
def __init__(
@@ -13,24 +15,39 @@ class BatchWorker:
):
self.l = logging.getLogger('solo_turnier.batch')
self.config = config
self.importPath = None
def run(self):
def prepare(self):
self.importPath = self.config.importHtmlPath()
if self.importPath is None:
self.l.debug('No HTML import path was provided.')
self.importPath = tkinter.filedialog.askdirectory(mustexist=True, title='HMTL Export auswählen')
if self.importPath is None or len(self.importPath) == 0:
self.l.critical('Import path was not selected. Aborting.')
tkinter.simpledialog.messagebox.showerror(
title='Invalid HTML path selected',
message='You did not select an appropriate folder. Aborting now.'
)
exit(1)
self.l.debug('Using import path %s', self.importPath)
def run(self, removeFilteredParicipants=True):
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.info('Checking for feasible preview HTML export files in "%s"', self.importPath)
htmlCandidatesPreview = locator.findPreviewRoundCandidates(self.importPath)
self.l.debug('Found HTML file candidates for preview rounds: %s', htmlCandidatesPreview)
htmlResultFiles = locator.findCandidates(self.config.importHtmlPath())
htmlResultFiles = locator.findCandidates(self.importPath)
self.l.debug('Using HTML result files for result extraction: %s', htmlResultFiles)
worker = solo_turnier.worker.Worker()
importedData = worker.collectAllData(htmlCandidatesPreview, htmlResultFiles)
combinedData = worker.combineData(importedData)
if not self.config.showAllParticipants():
worker.filterOutFinalists(combinedData)
worker.filterOutFinalists(combinedData, removeFilteredParicipants)
consoleOutputtter = solo_turnier.output.ConsoleOutputter()
consoleOutputtter.output(combinedData)
return combinedData

View File

@@ -6,9 +6,11 @@ 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('--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('html', help='The path from where to look for HTML export files', nargs=1, default=['.'])
parser.add_argument('html', help='The path from where to look for HTML export files', nargs='?', default=None)
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')
@@ -31,10 +33,14 @@ class Cli:
l.setLevel(logLevel)
def showGUI(self):
return self.__args.gui
# return self.__args.gui
return False
def startFlaskServer(self):
return self.__args.flask
def importHtmlPath(self):
return self.__args.html[0]
return self.__args.html
def importCSVPath(self):
return self.__args.import_from[0]
@@ -47,3 +53,6 @@ class Cli:
def showAllParticipants(self):
return self.__args.all_participants
def getPort(self):
return int(self.__args.port)

26
src/solo_turnier/flask.py Normal file
View File

@@ -0,0 +1,26 @@
import flask
import solo_turnier
def startFlask(
batchWorker: solo_turnier.batch.BatchWorker,
debug: bool = False,
port: int = 8082,
showOnlyFinalists: bool = True
):
app = flask.Flask(__name__)
@app.route('/')
def index():
combinedData = batchWorker.run(False)
return flask.render_template('index.html', data=combinedData)
@app.get('/custom.css')
def css():
ret = flask.render_template(
'custom.css',
onlyFinalists=showOnlyFinalists
)
return flask.Response(ret, mimetype='text/css')
app.run(host='0.0.0.0', port=port, debug=debug)

View File

@@ -0,0 +1,47 @@
.tab-summary {
width: 100%;
border-collapse: collapse;
}
.tab-summary tr:nth-of-type(even) {
background-color: cyan;
}
.tab-summary td {
text-align: center;
}
.tab-summary td .competition-place {
font-size: smaller;
font-weight: 300;
font-style: italic;
}
.tab-summary .no-finalist {
color: gray;
}
@media print {
@page {
size: landscape;
}
@page portrait {
size: portrait;
}
/* body {
size: landscape;
page-orientation: rotate-right;
} */
.section,
.section table tr,
.section table td
{
page-break-inside: avoid;
}
.tab-summary .no-finalist {
color: gray;
}
}

View File

@@ -0,0 +1,5 @@
{% if onlyFinalists %}
.no-finalist {
display: none;
}
{% endif %}

View File

@@ -0,0 +1,57 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Solo Turnier Auswertung</title>
{# <meta name="description" content="Webpage for xxxx"> #}
<!-- http://meyerweb.com/eric/tools/css/reset/ -->
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<link rel="stylesheet" href="/custom.css">
</head>
<body>
{# <h1>Finalauswertung Solo-Turniere</h1> #}
{% for group in data.groups %}
{% block groupBlk scoped %}
<div class="section">
<h1>Auswertung Gruppe {{ group.name }}</h1>
<table class="tab-summary">
<tr>
<th>Teilnehmer</th>
{% for dance in data.results[group].dances %}
<th>{{ dance }}</th>
{% endfor %}
</tr>
{% set activeGroup = data.results[group].results %}
{% for participant, results in activeGroup|dictsort() %}
{% block participantGrp scoped %}
{% set rowCls = "" %}
{% if not participant.finalist %}
{% set rowCls = "no-finalist" %}
{% endif %}
<tr class="{{ rowCls }}">
<td>{{ participant.name }} ({{ participant.id }})</td>
{% for dance in data.results[group].dances %}
{% block danceResult scoped %}
{% set res = activeGroup[participant][loop.index0] %}
<td>
{% if res is not none %}
{% if not participant.finalist %}
Kein/e Finalist/in
{% endif %}
{{ res.getNativePlace() }} ({{ res.nativeClass }}) <br />
<span class="competition-place">
{{ res.getPlace() }} in {{ res.competitionClass }}
</span>
{% endif %}
</td>
{% endblock %}
{% endfor %}
</tr>
{% endblock %}
{% endfor %}
</table>
</div>
{% endblock %}
{% endfor %}
</body>
</html>

View File

@@ -26,6 +26,7 @@ class HtmlPreviewParticipant:
self.id = id
groupParser = group.GroupParser()
self.group = groupParser.parseClass(group_)
self.finalist = None
def __eq__(self, o):
if type(o) != HtmlPreviewParticipant:
@@ -39,6 +40,9 @@ class HtmlPreviewParticipant:
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
@@ -201,6 +205,18 @@ class SingleParticipantResult:
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]]):

View File

@@ -651,7 +651,7 @@ class Worker:
# self.l.log(5, '(Partially) fixed places: %s', (data))
def filterOutFinalists(self, data: types.State4):
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()
@@ -668,10 +668,13 @@ class Worker:
finalist = True in mapped
self.l.log(5,'Check for finalist (in dances %s): %s', mapped, finalist)
if not 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)
for droppedParticipant in droppedParticipants:
data.results[group].results.pop(droppedParticipant)
pass
if filterOut:
for droppedParticipant in droppedParticipants:
data.results[group].results.pop(droppedParticipant)