Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6f11554c18 | |||
| 03fed1e1e4 | |||
| be5ac238bc | |||
| 5015a4e4e1 | |||
| 90f82e677e | |||
| fac3fe1b34 | |||
| 25135cc7d9 | |||
| 77c156b7db | |||
| 5140244d13 | |||
| da7a15fc0e | |||
| a8be846f36 |
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[Application]
|
||||
name=Solo Auswertung
|
||||
version=0.9.0
|
||||
version=0.9.5
|
||||
# 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
|
||||
|
||||
|
||||
16
src/main.py
16
src/main.py
@@ -15,11 +15,23 @@ def main():
|
||||
l = __initLogging()
|
||||
cli = solo_turnier.cli.Cli(l)
|
||||
|
||||
batchWorker = solo_turnier.batch.BatchWorker(cli)
|
||||
|
||||
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()
|
||||
|
||||
@@ -13,3 +13,4 @@ from . import worker
|
||||
from . import output
|
||||
|
||||
from . import batch
|
||||
from . import flask
|
||||
|
||||
@@ -14,7 +14,7 @@ class BatchWorker:
|
||||
self.l = logging.getLogger('solo_turnier.batch')
|
||||
self.config = config
|
||||
|
||||
def run(self):
|
||||
def run(self, removeFilteredParicipants=True):
|
||||
self.l.debug(self.config.__dict__)
|
||||
|
||||
locator = solo_turnier.html_locator.HtmlLocator()
|
||||
@@ -29,8 +29,6 @@ class BatchWorker:
|
||||
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
|
||||
|
||||
@@ -6,7 +6,9 @@ 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('-o', '--output', help='Set the output path of the script', nargs=1, default=[None])
|
||||
@@ -31,8 +33,12 @@ 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]
|
||||
|
||||
@@ -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
26
src/solo_turnier/flask.py
Normal 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)
|
||||
@@ -8,6 +8,10 @@ from .types import HtmlPreviewImport as HtmlImport, HtmlResultImport
|
||||
from .group import GroupParser
|
||||
from .competition_class import CompetitionClassParser
|
||||
|
||||
class IncompleteRoundException(Exception):
|
||||
def __init__(self, *args):
|
||||
super(IncompleteRoundException, self).__init__(*args)
|
||||
|
||||
class HtmlParser:
|
||||
|
||||
def __init__(self, text: str, fileName: str = None):
|
||||
@@ -73,7 +77,8 @@ class HtmlParser:
|
||||
def __parseFirstTable(table):
|
||||
roundName = table.tr.td.contents[0]
|
||||
if roundName != 'Endrunde':
|
||||
raise Exception('Could not parse HTML file')
|
||||
self.l.warning('Found table with round name %s.', roundName)
|
||||
raise IncompleteRoundException('Could not parse HTML file')
|
||||
|
||||
__parseRows(table.find_all('tr')[2:], True)
|
||||
|
||||
@@ -82,10 +87,14 @@ class HtmlParser:
|
||||
__parseRows(table.find_all('tr'), False)
|
||||
|
||||
tables = self.soup.find('div', class_='extract').find_all('table')
|
||||
if len(tables) > 0:
|
||||
__parseFirstTable(tables[0])
|
||||
|
||||
try:
|
||||
if len(tables) > 0:
|
||||
__parseFirstTable(tables[0])
|
||||
|
||||
__parseRemainingTables(tables[1:])
|
||||
__parseRemainingTables(tables[1:])
|
||||
except IncompleteRoundException:
|
||||
pass
|
||||
|
||||
# title = self.soup.find('div', class_='eventhead').table.tr.td.contents[0]
|
||||
|
||||
|
||||
47
src/solo_turnier/static/style.css
Normal file
47
src/solo_turnier/static/style.css
Normal 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;
|
||||
}
|
||||
}
|
||||
5
src/solo_turnier/templates/custom.css
Normal file
5
src/solo_turnier/templates/custom.css
Normal file
@@ -0,0 +1,5 @@
|
||||
{% if onlyFinalists %}
|
||||
.no-finalist {
|
||||
display: none;
|
||||
}
|
||||
{% endif %}
|
||||
57
src/solo_turnier/templates/index.html
Normal file
57
src/solo_turnier/templates/index.html
Normal 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>
|
||||
@@ -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]]):
|
||||
|
||||
@@ -177,10 +177,14 @@ class ResultExtractor:
|
||||
for filePair in files:
|
||||
with open(filePair[0], 'r') as fp:
|
||||
text = fp.read()
|
||||
with open(filePair[1], 'r') as fp:
|
||||
textTab = fp.read()
|
||||
parser = html_parser.HtmlParser(text, filePair[0])
|
||||
parserTab = html_parser.HtmlParser(textTab, filePair[1])
|
||||
|
||||
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()
|
||||
@@ -247,8 +251,11 @@ class ResultExtractor:
|
||||
self.l.debug('Extracting data from file %s', fileName)
|
||||
self._analyzeSingleParser(parsers[fileNameTuple][0], ret)
|
||||
|
||||
self.l.debug('Fetching individual result of combined competitions in %s', fileName)
|
||||
self._analyzeIndividualResults(parsers[fileNameTuple][1], 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
|
||||
|
||||
@@ -651,7 +658,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 +675,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)
|
||||
|
||||
Reference in New Issue
Block a user